diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000000..d25d71bcc9
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,4 @@
+.git
+.github
+resources/materials
+CuraEngine
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index 97c849144d..c69cf91433 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -6,7 +6,7 @@ Before filing, PLEASE check if the issue already exists (either open or closed)
Also, please note the application version in the title of the issue. For example: "[3.2.1] Cannot connect to 3rd-party printer". Please do not write thigns like "Request:" or "[BUG]" in the title; this is what labels are for.
It is also helpful to attach a project (.3mf or .curaproject) file and Cura log file so we can debug issues quicker.
-Information about how to find the log file can be found at https://github.com/Ultimaker/Cura/wiki/Cura-Preferences-and-Settings-Locations. To upload a project, we recommend http://wetransfer.com, but other file hosts like Google Drive or Dropbox work well too.
+Information about how to find the log file can be found at https://github.com/Ultimaker/Cura/wiki/Cura-Preferences-and-Settings-Locations. To upload a project, try changing the extension to e.g. .curaproject.3mf.zip so that github accepts uploading the file. Otherwise we recommend http://wetransfer.com, but other file hosts like Google Drive or Dropbox work well too.
Thank you for using Cura!
-->
@@ -26,6 +26,9 @@ Thank you for using Cura!
**Display Driver**
+**Printer**
+
+
**Steps to Reproduce**
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000..68255c56b9
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,45 @@
+FROM ultimaker/cura-build-environment:1
+
+# Environment vars for easy configuration
+ENV CURA_APP_DIR=/srv/cura
+
+# Ensure our sources dir exists
+RUN mkdir $CURA_APP_DIR
+
+# Setup CuraEngine
+ENV CURA_ENGINE_BRANCH=master
+WORKDIR $CURA_APP_DIR
+RUN git clone -b $CURA_ENGINE_BRANCH --depth 1 https://github.com/Ultimaker/CuraEngine
+WORKDIR $CURA_APP_DIR/CuraEngine
+RUN mkdir build
+WORKDIR $CURA_APP_DIR/CuraEngine/build
+RUN cmake3 ..
+RUN make
+RUN make install
+
+# TODO: setup libCharon
+
+# Setup Uranium
+ENV URANIUM_BRANCH=master
+WORKDIR $CURA_APP_DIR
+RUN git clone -b $URANIUM_BRANCH --depth 1 https://github.com/Ultimaker/Uranium
+
+# Setup materials
+ENV MATERIALS_BRANCH=master
+WORKDIR $CURA_APP_DIR
+RUN git clone -b $MATERIALS_BRANCH --depth 1 https://github.com/Ultimaker/fdm_materials materials
+
+# Setup Cura
+WORKDIR $CURA_APP_DIR/Cura
+ADD . .
+RUN mv $CURA_APP_DIR/materials resources/materials
+
+# Make sure Cura can find CuraEngine
+RUN ln -s /usr/local/bin/CuraEngine $CURA_APP_DIR/Cura
+
+# Run Cura
+WORKDIR $CURA_APP_DIR/Cura
+ENV PYTHONPATH=${PYTHONPATH}:$CURA_APP_DIR/Uranium
+RUN chmod +x ./CuraEngine
+RUN chmod +x ./run_in_docker.sh
+CMD "./run_in_docker.sh"
diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py
index b929f0f8f1..6da8f33fff 100755
--- a/cura/BuildVolume.py
+++ b/cura/BuildVolume.py
@@ -111,6 +111,9 @@ class BuildVolume(SceneNode):
# but it does not update the disallowed areas after material change
Application.getInstance().getMachineManager().activeStackChanged.connect(self._onStackChanged)
+ # Enable and disable extruder
+ Application.getInstance().getMachineManager().extruderChanged.connect(self.updateNodeBoundaryCheck)
+
# list of settings which were updated
self._changed_settings_since_last_rebuild = []
@@ -133,6 +136,7 @@ class BuildVolume(SceneNode):
if active_extruder_changed is not None:
node.callDecoration("getActiveExtruderChangedSignal").disconnect(self._updateDisallowedAreasAndRebuild)
node.decoratorsChanged.disconnect(self._updateNodeListeners)
+ self._updateDisallowedAreasAndRebuild() # make sure we didn't miss anything before we updated the node listeners
self._scene_objects = new_scene_objects
self._onSettingPropertyChanged("print_sequence", "value") # Create fake event, so right settings are triggered.
@@ -147,7 +151,6 @@ class BuildVolume(SceneNode):
active_extruder_changed = node.callDecoration("getActiveExtruderChangedSignal")
if active_extruder_changed is not None:
active_extruder_changed.connect(self._updateDisallowedAreasAndRebuild)
- self._updateDisallowedAreasAndRebuild()
def setWidth(self, width):
if width is not None:
@@ -217,30 +220,35 @@ class BuildVolume(SceneNode):
group_nodes.append(node) # Keep list of affected group_nodes
if node.callDecoration("isSliceable") or node.callDecoration("isGroup"):
- node._outside_buildarea = False
- bbox = node.getBoundingBox()
-
- # Mark the node as outside the build volume if the bounding box test fails.
- if build_volume_bounding_box.intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection:
- node._outside_buildarea = True
+ if node.collidesWithBbox(build_volume_bounding_box):
+ node.setOutsideBuildArea(True)
continue
- convex_hull = node.callDecoration("getConvexHull")
- if convex_hull:
- if not convex_hull.isValid():
- return
- # Check for collisions between disallowed areas and the object
- for area in self.getDisallowedAreas():
- overlap = convex_hull.intersectsPolygon(area)
- if overlap is None:
- continue
- node._outside_buildarea = True
- continue
+ if node.collidesWithArea(self.getDisallowedAreas()):
+ node.setOutsideBuildArea(True)
+ continue
+
+ # Mark the node as outside build volume if the set extruder is disabled
+ extruder_position = node.callDecoration("getActiveExtruderPosition")
+ if not self._global_container_stack.extruders[extruder_position].isEnabled:
+ node.setOutsideBuildArea(True)
+ continue
+
+ node.setOutsideBuildArea(False)
# Group nodes should override the _outside_buildarea property of their children.
for group_node in group_nodes:
- for child_node in group_node.getAllChildren():
- child_node._outside_buildarea = group_node._outside_buildarea
+ children = group_node.getAllChildren()
+
+ # Check if one or more children are non-printable and if so, set the parent as non-printable:
+ for child_node in children:
+ if child_node.isOutsideBuildArea():
+ group_node.setOutsideBuildArea(True)
+ break
+
+ # Apply results of the check to all children of the group:
+ for child_node in children:
+ child_node.setOutsideBuildArea(group_node.isOutsideBuildArea())
## Update the outsideBuildArea of a single node, given bounds or current build volume
def checkBoundsAndUpdate(self, node: CuraSceneNode, bounds: Optional[AxisAlignedBox] = None):
@@ -260,24 +268,20 @@ class BuildVolume(SceneNode):
build_volume_bounding_box = bounds
if node.callDecoration("isSliceable") or node.callDecoration("isGroup"):
- bbox = node.getBoundingBox()
-
- # Mark the node as outside the build volume if the bounding box test fails.
- if build_volume_bounding_box.intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection:
+ if node.collidesWithBbox(build_volume_bounding_box):
+ node.setOutsideBuildArea(True)
+ return
+
+ if node.collidesWithArea(self.getDisallowedAreas()):
+ node.setOutsideBuildArea(True)
+ return
+
+ # Mark the node as outside build volume if the set extruder is disabled
+ extruder_position = node.callDecoration("getActiveExtruderPosition")
+ if not self._global_container_stack.extruders[extruder_position].isEnabled:
node.setOutsideBuildArea(True)
return
- convex_hull = self.callDecoration("getConvexHull")
- if convex_hull:
- if not convex_hull.isValid():
- return
- # Check for collisions between disallowed areas and the object
- for area in self.getDisallowedAreas():
- overlap = convex_hull.intersectsPolygon(area)
- if overlap is None:
- continue
- node.setOutsideBuildArea(True)
- return
node.setOutsideBuildArea(False)
## Recalculates the build volume & disallowed areas.
diff --git a/cura/CrashHandler.py b/cura/CrashHandler.py
index d74b48a53a..f51174aec0 100644
--- a/cura/CrashHandler.py
+++ b/cura/CrashHandler.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import platform
@@ -14,10 +14,11 @@ import urllib.request
import urllib.error
import shutil
-from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, QUrl
+from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, Qt, QUrl
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QTextEdit, QGroupBox, QCheckBox, QPushButton
from PyQt5.QtGui import QDesktopServices
+from UM.Resources import Resources
from UM.Application import Application
from UM.Logger import Logger
from UM.View.GL.OpenGL import OpenGL
@@ -84,14 +85,14 @@ class CrashHandler:
dialog = QDialog()
dialog.setMinimumWidth(500)
dialog.setMinimumHeight(170)
- dialog.setWindowTitle(catalog.i18nc("@title:window", "Cura Crashed"))
+ dialog.setWindowTitle(catalog.i18nc("@title:window", "Cura can't startup"))
dialog.finished.connect(self._closeEarlyCrashDialog)
layout = QVBoxLayout(dialog)
label = QLabel()
- label.setText(catalog.i18nc("@label crash message", """
A fatal error has occurred.
- Unfortunately, Cura encountered an unrecoverable error during start up. It was possibly caused by some incorrect configuration files. We suggest to backup and reset your configuration.
+ label.setText(catalog.i18nc("@label crash message", """Oops, Ultimaker Cura has encountered something that doesn't seem right.
+ We encountered an unrecoverable error during start up. It was possibly caused by some incorrect configuration files. We suggest to backup and reset your configuration.
Backups can be found in the configuration folder.
Please send us this Crash Report to fix the problem.
"""))
@@ -219,7 +220,7 @@ class CrashHandler:
def _messageWidget(self):
label = QLabel()
- label.setText(catalog.i18nc("@label crash message", """A fatal error has occurred. Please send us this Crash Report to fix the problem
+ label.setText(catalog.i18nc("@label crash message", """A fatal error has occurred in Cura. Please send us this Crash Report to fix the problem
Please use the "Send report" button to post a bug report automatically to our servers
"""))
@@ -258,7 +259,7 @@ class CrashHandler:
opengl_instance = OpenGL.getInstance()
if not opengl_instance:
self.data["opengl"] = {"version": "n/a", "vendor": "n/a", "type": "n/a"}
- return catalog.i18nc("@label", "not yet initialised ")
+ return catalog.i18nc("@label", "Not yet initialized ")
info = ""
info += catalog.i18nc("@label OpenGL version", "OpenGL Version: {version} ").format(version = opengl_instance.getOpenGLVersion())
diff --git a/cura/CuraActions.py b/cura/CuraActions.py
index f517ec4217..75338f17b6 100644
--- a/cura/CuraActions.py
+++ b/cura/CuraActions.py
@@ -109,10 +109,6 @@ class CuraActions(QObject):
nodes_to_change = []
for node in Selection.getAllSelectedObjects():
- # Do not change any nodes that already have the right extruder set.
- if node.callDecoration("getActiveExtruder") == extruder_id:
- continue
-
# If the node is a group, apply the active extruder to all children of the group.
if node.callDecoration("isGroup"):
for grouped_node in BreadthFirstIterator(node):
@@ -125,6 +121,10 @@ class CuraActions(QObject):
nodes_to_change.append(grouped_node)
continue
+ # Do not change any nodes that already have the right extruder set.
+ if node.callDecoration("getActiveExtruder") == extruder_id:
+ continue
+
nodes_to_change.append(node)
if not nodes_to_change:
diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py
index 2ca321e4cc..6d5bd34ee4 100755
--- a/cura/CuraApplication.py
+++ b/cura/CuraApplication.py
@@ -1,9 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-#Type hinting.
-from typing import Dict
-from PyQt5.QtCore import QObject
+from PyQt5.QtCore import QObject, QTimer
from PyQt5.QtNetwork import QLocalServer
from PyQt5.QtNetwork import QLocalSocket
@@ -59,18 +57,22 @@ from cura.Machines.Models.BuildPlateModel import BuildPlateModel
from cura.Machines.Models.NozzleModel import NozzleModel
from cura.Machines.Models.QualityProfilesDropDownMenuModel import QualityProfilesDropDownMenuModel
from cura.Machines.Models.CustomQualityProfilesDropDownMenuModel import CustomQualityProfilesDropDownMenuModel
-
from cura.Machines.Models.MultiBuildPlateModel import MultiBuildPlateModel
-
from cura.Machines.Models.MaterialManagementModel import MaterialManagementModel
from cura.Machines.Models.GenericMaterialsModel import GenericMaterialsModel
from cura.Machines.Models.BrandMaterialsModel import BrandMaterialsModel
+from cura.Machines.Models.QualityManagementModel import QualityManagementModel
+from cura.Machines.Models.QualitySettingsModel import QualitySettingsModel
+from cura.Machines.Models.MachineManagementModel import MachineManagementModel
+
+from cura.Machines.Models.SettingVisibilityPresetsModel import SettingVisibilityPresetsModel
+
+from cura.Machines.MachineErrorChecker import MachineErrorChecker
from cura.Settings.SettingInheritanceManager import SettingInheritanceManager
from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager
from cura.Machines.VariantManager import VariantManager
-from cura.Machines.Models.QualityManagementModel import QualityManagementModel
from . import PlatformPhysics
from . import BuildVolume
@@ -87,7 +89,6 @@ from cura.Settings.ExtruderManager import ExtruderManager
from cura.Settings.UserChangesModel import UserChangesModel
from cura.Settings.ExtrudersModel import ExtrudersModel
from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler
-from cura.Machines.Models.QualitySettingsModel import QualitySettingsModel
from cura.Settings.ContainerManager import ContainerManager
from cura.ObjectsModel import ObjectsModel
@@ -98,7 +99,6 @@ from PyQt5.QtGui import QColor, QIcon
from PyQt5.QtWidgets import QMessageBox
from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType
-from configparser import ConfigParser
import sys
import os.path
import numpy
@@ -138,15 +138,10 @@ class CuraApplication(QtApplication):
MachineStack = Resources.UserType + 7
ExtruderStack = Resources.UserType + 8
DefinitionChangesContainer = Resources.UserType + 9
+ SettingVisibilityPreset = Resources.UserType + 10
Q_ENUMS(ResourceTypes)
- # FIXME: This signal belongs to the MachineManager, but the CuraEngineBackend plugin requires on it.
- # Because plugins are initialized before the ContainerRegistry, putting this signal in MachineManager
- # will make it initialized before ContainerRegistry does, and it won't find the active machine, thus
- # Cura will always show the Add Machine Dialog upon start.
- stacksValidationFinished = pyqtSignal() # Emitted whenever a validation is finished
-
def __init__(self, **kwargs):
# this list of dir names will be used by UM to detect an old cura directory
for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "user", "variants"]:
@@ -191,6 +186,7 @@ class CuraApplication(QtApplication):
Resources.addStorageType(self.ResourceTypes.ExtruderStack, "extruders")
Resources.addStorageType(self.ResourceTypes.MachineStack, "machine_instances")
Resources.addStorageType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes")
+ Resources.addStorageType(self.ResourceTypes.SettingVisibilityPreset, "setting_visibility")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality_changes")
@@ -208,10 +204,10 @@ class CuraApplication(QtApplication):
UM.VersionUpgradeManager.VersionUpgradeManager.getInstance().setCurrentVersions(
{
("quality_changes", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.QualityInstanceContainer, "application/x-uranium-instancecontainer"),
- ("machine_stack", ContainerStack.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.MachineStack, "application/x-cura-globalstack"),
- ("extruder_train", ContainerStack.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.ExtruderStack, "application/x-cura-extruderstack"),
- ("preferences", Preferences.Version * 1000000 + self.SettingVersion): (Resources.Preferences, "application/x-uranium-preferences"),
- ("user", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.UserInstanceContainer, "application/x-uranium-instancecontainer"),
+ ("machine_stack", ContainerStack.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.MachineStack, "application/x-cura-globalstack"),
+ ("extruder_train", ContainerStack.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.ExtruderStack, "application/x-cura-extruderstack"),
+ ("preferences", Preferences.Version * 1000000 + self.SettingVersion): (Resources.Preferences, "application/x-uranium-preferences"),
+ ("user", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.UserInstanceContainer, "application/x-uranium-instancecontainer"),
("definition_changes", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.DefinitionChangesContainer, "application/x-uranium-instancecontainer"),
}
)
@@ -223,12 +219,15 @@ class CuraApplication(QtApplication):
self._machine_manager = None # This is initialized on demand.
self._extruder_manager = None
self._material_manager = None
+ self._quality_manager = None
self._object_manager = None
self._build_plate_model = None
self._multi_build_plate_model = None
+ self._setting_visibility_presets_model = None
self._setting_inheritance_manager = None
self._simple_mode_settings_manager = None
self._cura_scene_controller = None
+ self._machine_error_checker = None
self._additional_components = {} # Components to add to certain areas in the interface
@@ -285,10 +284,15 @@ class CuraApplication(QtApplication):
self._preferred_mimetype = ""
self._i18n_catalog = i18nCatalog("cura")
- self.getController().getScene().sceneChanged.connect(self.updatePlatformActivity)
+ self._update_platform_activity_timer = QTimer()
+ self._update_platform_activity_timer.setInterval(500)
+ self._update_platform_activity_timer.setSingleShot(True)
+ self._update_platform_activity_timer.timeout.connect(self.updatePlatformActivity)
+
+ self.getController().getScene().sceneChanged.connect(self.updatePlatformActivityDelayed)
self.getController().toolOperationStopped.connect(self._onToolOperationStopped)
self.getController().contextMenuRequested.connect(self._onContextMenuRequested)
- self.getCuraSceneController().activeBuildPlateChanged.connect(self.updatePlatformActivity)
+ self.getCuraSceneController().activeBuildPlateChanged.connect(self.updatePlatformActivityDelayed)
Resources.addType(self.ResourceTypes.QmlFiles, "qml")
Resources.addType(self.ResourceTypes.Firmware, "firmware")
@@ -375,20 +379,6 @@ class CuraApplication(QtApplication):
preferences.setDefault("local_file/last_used_type", "text/x-gcode")
- setting_visibily_preset_names = self.getVisibilitySettingPresetTypes()
- preferences.setDefault("general/visible_settings_preset", setting_visibily_preset_names)
-
- preset_setting_visibility_choice = Preferences.getInstance().getValue("general/preset_setting_visibility_choice")
-
- default_preset_visibility_group_name = "Basic"
- if preset_setting_visibility_choice == "" or preset_setting_visibility_choice is None:
- if preset_setting_visibility_choice not in setting_visibily_preset_names:
- preset_setting_visibility_choice = default_preset_visibility_group_name
-
- visible_settings = self.getVisibilitySettingPreset(settings_preset_name = preset_setting_visibility_choice)
- preferences.setDefault("general/visible_settings", visible_settings)
- preferences.setDefault("general/preset_setting_visibility_choice", preset_setting_visibility_choice)
-
self.applicationShuttingDown.connect(self.saveSettings)
self.engineCreatedSignal.connect(self._onEngineCreated)
@@ -404,91 +394,6 @@ class CuraApplication(QtApplication):
CuraApplication.Created = True
- @pyqtSlot(str, result = str)
- def getVisibilitySettingPreset(self, settings_preset_name) -> str:
- result = self._loadPresetSettingVisibilityGroup(settings_preset_name)
- formatted_preset_settings = self._serializePresetSettingVisibilityData(result)
-
- return formatted_preset_settings
-
- ## Serialise the given preset setting visibitlity group dictionary into a string which is concatenated by ";"
- #
- def _serializePresetSettingVisibilityData(self, settings_data: dict) -> str:
- result_string = ""
-
- for key in settings_data:
- result_string += key + ";"
- for value in settings_data[key]:
- result_string += value + ";"
-
- return result_string
-
- ## Load the preset setting visibility group with the given name
- #
- def _loadPresetSettingVisibilityGroup(self, visibility_preset_name) -> Dict[str, str]:
- preset_dir = Resources.getPath(Resources.PresetSettingVisibilityGroups)
-
- result = {}
- right_preset_found = False
-
- for item in os.listdir(preset_dir):
- file_path = os.path.join(preset_dir, item)
- if not os.path.isfile(file_path):
- continue
-
- parser = ConfigParser(allow_no_value = True) # accept options without any value,
-
- try:
- parser.read([file_path])
-
- if not parser.has_option("general", "name"):
- continue
-
- if parser["general"]["name"] == visibility_preset_name:
- right_preset_found = True
- for section in parser.sections():
- if section == 'general':
- continue
- else:
- section_settings = []
- for option in parser[section].keys():
- section_settings.append(option)
-
- result[section] = section_settings
-
- if right_preset_found:
- break
-
- except Exception as e:
- Logger.log("e", "Failed to load setting visibility preset %s: %s", file_path, str(e))
-
- return result
-
- ## Check visibility setting preset folder and returns available types
- #
- def getVisibilitySettingPresetTypes(self):
- preset_dir = Resources.getPath(Resources.PresetSettingVisibilityGroups)
- result = {}
-
- for item in os.listdir(preset_dir):
- file_path = os.path.join(preset_dir, item)
- if not os.path.isfile(file_path):
- continue
-
- parser = ConfigParser(allow_no_value=True) # accept options without any value,
-
- try:
- parser.read([file_path])
-
- if not parser.has_option("general", "name") and not parser.has_option("general", "weight"):
- continue
-
- result[parser["general"]["weight"]] = parser["general"]["name"]
-
- except Exception as e:
- Logger.log("e", "Failed to load setting preset %s: %s", file_path, str(e))
-
- return result
def _onEngineCreated(self):
self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
@@ -546,27 +451,18 @@ class CuraApplication(QtApplication):
@pyqtSlot(str)
def discardOrKeepProfileChangesClosed(self, option):
+ global_stack = self.getGlobalContainerStack()
if option == "discard":
- global_stack = self.getGlobalContainerStack()
- for extruder in self._extruder_manager.getMachineExtruders(global_stack.getId()):
- extruder.getTop().clear()
- global_stack.getTop().clear()
+ for extruder in global_stack.extruders.values():
+ extruder.userChanges.clear()
+ global_stack.userChanges.clear()
# if the user decided to keep settings then the user settings should be re-calculated and validated for errors
# before slicing. To ensure that slicer uses right settings values
elif option == "keep":
- global_stack = self.getGlobalContainerStack()
- for extruder in self._extruder_manager.getMachineExtruders(global_stack.getId()):
- user_extruder_container = extruder.getTop()
- if user_extruder_container:
- user_extruder_container.update()
-
- user_global_container = global_stack.getTop()
- if user_global_container:
- user_global_container.update()
-
- # notify listeners that quality has changed (after user selected discard or keep)
- self.getMachineManager().activeQualityChanged.emit()
+ for extruder in global_stack.extruders.values():
+ extruder.userChanges.update()
+ global_stack.userChanges.update()
@pyqtSlot(int)
def messageBoxClosed(self, button):
@@ -602,8 +498,13 @@ class CuraApplication(QtApplication):
def getStaticVersion(cls):
return CuraVersion
+ ## Handle removing the unneeded plugins
+ # \sa PluginRegistry
+ def _removePlugins(self):
+ self._plugin_registry.removePlugins()
+
## Handle loading of all plugin types (and the backend explicitly)
- # \sa PluginRegistery
+ # \sa PluginRegistry
def _loadPlugins(self):
self._plugin_registry.addType("profile_reader", self._addProfileReader)
self._plugin_registry.addType("profile_writer", self._addProfileWriter)
@@ -742,19 +643,28 @@ class CuraApplication(QtApplication):
self.preRun()
container_registry = ContainerRegistry.getInstance()
+
+ Logger.log("i", "Initializing variant manager")
self._variant_manager = VariantManager(container_registry)
self._variant_manager.initialize()
+ Logger.log("i", "Initializing material manager")
from cura.Machines.MaterialManager import MaterialManager
self._material_manager = MaterialManager(container_registry, parent = self)
self._material_manager.initialize()
+ Logger.log("i", "Initializing quality manager")
from cura.Machines.QualityManager import QualityManager
self._quality_manager = QualityManager(container_registry, parent = self)
self._quality_manager.initialize()
+ Logger.log("i", "Initializing machine manager")
self._machine_manager = MachineManager(self)
+ Logger.log("i", "Initializing machine error checker")
+ self._machine_error_checker = MachineErrorChecker(self)
+ self._machine_error_checker.initialize()
+
# Check if we should run as single instance or not
self._setUpSingleInstanceServer()
@@ -767,6 +677,11 @@ class CuraApplication(QtApplication):
self._print_information = PrintInformation.PrintInformation()
self._cura_actions = CuraActions.CuraActions(self)
+ # Initialize setting visibility presets model
+ self._setting_visibility_presets_model = SettingVisibilityPresetsModel(self)
+ default_visibility_profile = self._setting_visibility_presets_model.getItem(0)
+ Preferences.getInstance().setDefault("general/visible_settings", ";".join(default_visibility_profile["settings"]))
+
# Detect in which mode to run and execute that mode
if self.getCommandLineOption("headless", False):
self.runWithoutGUI()
@@ -780,8 +695,11 @@ class CuraApplication(QtApplication):
self._openFile(file_name)
self.started = True
+ self.initializationFinished.emit()
self.exec_()
+ initializationFinished = pyqtSignal()
+
## Run Cura without GUI elements and interaction (server mode).
def runWithoutGUI(self):
self._use_gui = False
@@ -846,6 +764,13 @@ class CuraApplication(QtApplication):
def hasGui(self):
return self._use_gui
+ @pyqtSlot(result = QObject)
+ def getSettingVisibilityPresetsModel(self, *args) -> SettingVisibilityPresetsModel:
+ return self._setting_visibility_presets_model
+
+ def getMachineErrorChecker(self, *args) -> MachineErrorChecker:
+ return self._machine_error_checker
+
def getMachineManager(self, *args) -> MachineManager:
if self._machine_manager is None:
self._machine_manager = MachineManager(self)
@@ -960,6 +885,7 @@ class CuraApplication(QtApplication):
qmlRegisterType(BrandMaterialsModel, "Cura", 1, 0, "BrandMaterialsModel")
qmlRegisterType(MaterialManagementModel, "Cura", 1, 0, "MaterialManagementModel")
qmlRegisterType(QualityManagementModel, "Cura", 1, 0, "QualityManagementModel")
+ qmlRegisterType(MachineManagementModel, "Cura", 1, 0, "MachineManagementModel")
qmlRegisterSingletonType(QualityProfilesDropDownMenuModel, "Cura", 1, 0,
"QualityProfilesDropDownMenuModel", self.getQualityProfilesDropDownMenuModel)
@@ -968,6 +894,7 @@ class CuraApplication(QtApplication):
qmlRegisterType(NozzleModel, "Cura", 1, 0, "NozzleModel")
qmlRegisterType(MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler")
+ qmlRegisterType(SettingVisibilityPresetsModel, "Cura", 1, 0, "SettingVisibilityPresetsModel")
qmlRegisterType(QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel")
qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator")
qmlRegisterType(UserChangesModel, "Cura", 1, 0, "UserChangesModel")
@@ -1047,6 +974,10 @@ class CuraApplication(QtApplication):
def getSceneBoundingBoxString(self):
return self._i18n_catalog.i18nc("@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## mm.", "%(width).1f x %(depth).1f x %(height).1f mm") % {'width' : self._scene_bounding_box.width.item(), 'depth': self._scene_bounding_box.depth.item(), 'height' : self._scene_bounding_box.height.item()}
+ def updatePlatformActivityDelayed(self, node = None):
+ if node is not None and node.getMeshData() is not None:
+ self._update_platform_activity_timer.start()
+
## Update scene bounding box for current build plate
def updatePlatformActivity(self, node = None):
count = 0
@@ -1179,7 +1110,7 @@ class CuraApplication(QtApplication):
continue
if not node.getMeshData() and not node.callDecoration("isGroup"):
continue # Node that doesnt have a mesh and is not a group.
- if node.getParent() and node.getParent().callDecoration("isGroup"):
+ if node.getParent() and node.getParent().callDecoration("isGroup") or node.getParent().callDecoration("isSliceable"):
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
if not node.isSelectable():
continue # i.e. node with layer data
@@ -1354,8 +1285,11 @@ class CuraApplication(QtApplication):
def reloadAll(self):
Logger.log("i", "Reloading all loaded mesh data.")
nodes = []
+ has_merged_nodes = False
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
- if not isinstance(node, CuraSceneNode) or not node.getMeshData():
+ if not isinstance(node, CuraSceneNode) or not node.getMeshData() :
+ if node.getName() == 'MergedMesh':
+ has_merged_nodes = True
continue
nodes.append(node)
@@ -1369,10 +1303,14 @@ class CuraApplication(QtApplication):
job = ReadMeshJob(file_name)
job._node = node
job.finished.connect(self._reloadMeshFinished)
+ if has_merged_nodes:
+ job.finished.connect(self.updateOriginOfMergedMeshes)
+
job.start()
else:
Logger.log("w", "Unable to reload data because we don't have a filename.")
+
## Get logging data of the backend engine
# \returns \type{string} Logging data
@pyqtSlot(result = str)
@@ -1442,6 +1380,58 @@ class CuraApplication(QtApplication):
# Use the previously found center of the group bounding box as the new location of the group
group_node.setPosition(group_node.getBoundingBox().center)
+ group_node.setName("MergedMesh") # add a specific name to destinguis this node
+
+
+ ## Updates origin position of all merged meshes
+ # \param jobNode \type{Job} empty object which passed which is required by JobQueue
+ def updateOriginOfMergedMeshes(self, jobNode):
+ group_nodes = []
+ for node in DepthFirstIterator(self.getController().getScene().getRoot()):
+ if isinstance(node, CuraSceneNode) and node.getName() == "MergedMesh":
+
+ #checking by name might be not enough, the merged mesh should has "GroupDecorator" decorator
+ for decorator in node.getDecorators():
+ if isinstance(decorator, GroupDecorator):
+ group_nodes.append(node)
+ break
+
+ for group_node in group_nodes:
+ meshes = [node.getMeshData() for node in group_node.getAllChildren() if node.getMeshData()]
+
+ # Compute the center of the objects
+ object_centers = []
+ # Forget about the translation that the original objects have
+ zero_translation = Matrix(data=numpy.zeros(3))
+ for mesh, node in zip(meshes, group_node.getChildren()):
+ transformation = node.getLocalTransformation()
+ transformation.setTranslation(zero_translation)
+ transformed_mesh = mesh.getTransformed(transformation)
+ center = transformed_mesh.getCenterPosition()
+ if center is not None:
+ object_centers.append(center)
+
+ if object_centers and len(object_centers) > 0:
+ middle_x = sum([v.x for v in object_centers]) / len(object_centers)
+ middle_y = sum([v.y for v in object_centers]) / len(object_centers)
+ middle_z = sum([v.z for v in object_centers]) / len(object_centers)
+ offset = Vector(middle_x, middle_y, middle_z)
+ else:
+ offset = Vector(0, 0, 0)
+
+ # Move each node to the same position.
+ for mesh, node in zip(meshes, group_node.getChildren()):
+ transformation = node.getLocalTransformation()
+ transformation.setTranslation(zero_translation)
+ transformed_mesh = mesh.getTransformed(transformation)
+
+ # Align the object around its zero position
+ # and also apply the offset to center it inside the group.
+ node.setPosition(-transformed_mesh.getZeroPosition() - offset)
+
+ # Use the previously found center of the group bounding box as the new location of the group
+ group_node.setPosition(group_node.getBoundingBox().center)
+
@pyqtSlot()
def groupSelected(self):
@@ -1457,6 +1447,12 @@ class CuraApplication(QtApplication):
group_node.setPosition(center)
group_node.setCenterPosition(center)
+ # Remove nodes that are directly parented to another selected node from the selection so they remain parented
+ selected_nodes = Selection.getAllSelectedObjects().copy()
+ for node in selected_nodes:
+ if node.getParent() in selected_nodes and not node.getParent().callDecoration("isGroup"):
+ Selection.remove(node)
+
# Move selected nodes into the group-node
Selection.applyOperation(SetParentOperation, group_node)
@@ -1475,6 +1471,10 @@ class CuraApplication(QtApplication):
group_parent = node.getParent()
children = node.getChildren().copy()
for child in children:
+ # Ungroup only 1 level deep
+ if child.getParent() != node:
+ continue
+
# Set the parent of the children to the parent of the group-node
op.addOperation(SetParentOperation(child, group_parent))
@@ -1604,6 +1604,8 @@ class CuraApplication(QtApplication):
fixed_nodes.append(node_)
arranger = Arrange.create(fixed_nodes = fixed_nodes)
min_offset = 8
+ default_extruder_position = self.getMachineManager().defaultExtruderPosition
+ default_extruder_id = self._global_container_stack.extruders[default_extruder_position].getId()
for original_node in nodes:
@@ -1669,6 +1671,8 @@ class CuraApplication(QtApplication):
op = AddSceneNodeOperation(node, scene.getRoot())
op.push()
+
+ node.callDecoration("setActiveExtruder", default_extruder_id)
scene.sceneChanged.emit(node)
self.fileCompleted.emit(filename)
@@ -1689,7 +1693,7 @@ class CuraApplication(QtApplication):
result = workspace_reader.preRead(file_path, show_dialog=False)
return result == WorkspaceReader.PreReadResult.accepted
except Exception as e:
- Logger.log("e", "Could not check file %s: %s", file_url, e)
+ Logger.logException("e", "Could not check file %s: %s", file_url)
return False
def _onContextMenuRequested(self, x: float, y: float) -> None:
diff --git a/cura/Machines/MachineErrorChecker.py b/cura/Machines/MachineErrorChecker.py
new file mode 100644
index 0000000000..37de4f30ce
--- /dev/null
+++ b/cura/Machines/MachineErrorChecker.py
@@ -0,0 +1,181 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+import time
+
+from collections import deque
+
+from PyQt5.QtCore import QObject, QTimer, pyqtSignal, pyqtProperty
+
+from UM.Application import Application
+from UM.Logger import Logger
+from UM.Settings.SettingDefinition import SettingDefinition
+from UM.Settings.Validator import ValidatorState
+
+
+#
+# This class performs setting error checks for the currently active machine.
+#
+# The whole error checking process is pretty heavy which can take ~0.5 secs, so it can cause GUI to lag.
+# The idea here is to split the whole error check into small tasks, each of which only checks a single setting key
+# in a stack. According to my profiling results, the maximal runtime for such a sub-task is <0.03 secs, which should
+# be good enough. Moreover, if any changes happened to the machine, we can cancel the check in progress without wait
+# for it to finish the complete work.
+#
+class MachineErrorChecker(QObject):
+
+ def __init__(self, parent = None):
+ super().__init__(parent)
+
+ self._global_stack = None
+
+ self._has_errors = True # Result of the error check, indicating whether there are errors in the stack
+ self._error_keys = set() # A set of settings keys that have errors
+ self._error_keys_in_progress = set() # The variable that stores the results of the currently in progress check
+
+ self._stacks_and_keys_to_check = None # a FIFO queue of tuples (stack, key) to check for errors
+
+ self._need_to_check = False # Whether we need to schedule a new check or not. This flag is set when a new
+ # error check needs to take place while there is already one running at the moment.
+ self._check_in_progress = False # Whether there is an error check running in progress at the moment.
+
+ self._application = Application.getInstance()
+ self._machine_manager = self._application.getMachineManager()
+
+ self._start_time = 0 # measure checking time
+
+ # This timer delays the starting of error check so we can react less frequently if the user is frequently
+ # changing settings.
+ self._error_check_timer = QTimer(self)
+ self._error_check_timer.setInterval(100)
+ self._error_check_timer.setSingleShot(True)
+
+ def initialize(self):
+ self._error_check_timer.timeout.connect(self._rescheduleCheck)
+
+ # Reconnect all signals when the active machine gets changed.
+ self._machine_manager.globalContainerChanged.connect(self._onMachineChanged)
+
+ # Whenever the machine settings get changed, we schedule an error check.
+ self._machine_manager.globalContainerChanged.connect(self.startErrorCheck)
+ self._machine_manager.globalValueChanged.connect(self.startErrorCheck)
+
+ self._onMachineChanged()
+
+ def _onMachineChanged(self):
+ if self._global_stack:
+ self._global_stack.propertyChanged.disconnect(self.startErrorCheck)
+ self._global_stack.containersChanged.disconnect(self.startErrorCheck)
+
+ for extruder in self._global_stack.extruders.values():
+ extruder.propertyChanged.disconnect(self.startErrorCheck)
+ extruder.containersChanged.disconnect(self.startErrorCheck)
+
+ self._global_stack = self._machine_manager.activeMachine
+
+ if self._global_stack:
+ self._global_stack.propertyChanged.connect(self.startErrorCheck)
+ self._global_stack.containersChanged.connect(self.startErrorCheck)
+
+ for extruder in self._global_stack.extruders.values():
+ extruder.propertyChanged.connect(self.startErrorCheck)
+ extruder.containersChanged.connect(self.startErrorCheck)
+
+ hasErrorUpdated = pyqtSignal()
+ needToWaitForResultChanged = pyqtSignal()
+ errorCheckFinished = pyqtSignal()
+
+ @pyqtProperty(bool, notify = hasErrorUpdated)
+ def hasError(self) -> bool:
+ return self._has_errors
+
+ @pyqtProperty(bool, notify = needToWaitForResultChanged)
+ def needToWaitForResult(self) -> bool:
+ return self._need_to_check or self._check_in_progress
+
+ # Starts the error check timer to schedule a new error check.
+ def startErrorCheck(self, *args):
+ if not self._check_in_progress:
+ self._need_to_check = True
+ self.needToWaitForResultChanged.emit()
+ self._error_check_timer.start()
+
+ # This function is called by the timer to reschedule a new error check.
+ # If there is no check in progress, it will start a new one. If there is any, it sets the "_need_to_check" flag
+ # to notify the current check to stop and start a new one.
+ def _rescheduleCheck(self):
+ if self._check_in_progress and not self._need_to_check:
+ self._need_to_check = True
+ self.needToWaitForResultChanged.emit()
+ return
+
+ self._error_keys_in_progress = set()
+ self._need_to_check = False
+ self.needToWaitForResultChanged.emit()
+
+ global_stack = self._machine_manager.activeMachine
+ if global_stack is None:
+ Logger.log("i", "No active machine, nothing to check.")
+ return
+
+ # Populate the (stack, key) tuples to check
+ self._stacks_and_keys_to_check = deque()
+ for stack in [global_stack] + list(global_stack.extruders.values()):
+ for key in stack.getAllKeys():
+ self._stacks_and_keys_to_check.append((stack, key))
+
+ self._application.callLater(self._checkStack)
+ self._start_time = time.time()
+ Logger.log("d", "New error check scheduled.")
+
+ def _checkStack(self):
+ if self._need_to_check:
+ Logger.log("d", "Need to check for errors again. Discard the current progress and reschedule a check.")
+ self._check_in_progress = False
+ self._application.callLater(self.startErrorCheck)
+ return
+
+ self._check_in_progress = True
+
+ # If there is nothing to check any more, it means there is no error.
+ if not self._stacks_and_keys_to_check:
+ # Finish
+ self._setResult(False)
+ return
+
+ # Get the next stack and key to check
+ stack, key = self._stacks_and_keys_to_check.popleft()
+
+ enabled = stack.getProperty(key, "enabled")
+ if not enabled:
+ self._application.callLater(self._checkStack)
+ return
+
+ validation_state = stack.getProperty(key, "validationState")
+ if validation_state is None:
+ # Setting is not validated. This can happen if there is only a setting definition.
+ # We do need to validate it, because a setting definitions value can be set by a function, which could
+ # be an invalid setting.
+ definition = stack.getSettingDefinition(key)
+ validator_type = SettingDefinition.getValidatorForType(definition.type)
+ if validator_type:
+ validator = validator_type(key)
+ validation_state = validator(stack)
+ if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError):
+ # Finish
+ self._setResult(True)
+ return
+
+ # Schedule the check for the next key
+ self._application.callLater(self._checkStack)
+
+ def _setResult(self, result: bool):
+ if result != self._has_errors:
+ self._has_errors = result
+ self.hasErrorUpdated.emit()
+ self._machine_manager.stacksValidationChanged.emit()
+ self._need_to_check = False
+ self._check_in_progress = False
+ self.needToWaitForResultChanged.emit()
+ self.errorCheckFinished.emit()
+ Logger.log("i", "Error check finished, result = %s, time = %0.1fs", result, time.time() - self._start_time)
diff --git a/cura/Machines/MaterialGroup.py b/cura/Machines/MaterialGroup.py
index 009778943a..93c8a227a8 100644
--- a/cura/Machines/MaterialGroup.py
+++ b/cura/Machines/MaterialGroup.py
@@ -1,9 +1,10 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
+from typing import List
+from cura.Machines.MaterialNode import MaterialNode #For type checking.
-#
-# A MaterialGroup represents a group of material InstanceContainers that are derived from a single material profile.
+## A MaterialGroup represents a group of material InstanceContainers that are derived from a single material profile.
# The main InstanceContainer which has the ID of the material profile file name is called the "root_material". For
# example: "generic_abs" is the root material (ID) of "generic_abs_ultimaker3" and "generic_abs_ultimaker3_AA_0.4",
# and "generic_abs_ultimaker3" and "generic_abs_ultimaker3_AA_0.4" are derived materials of "generic_abs".
@@ -15,12 +16,13 @@
# so "generic_abs_ultimaker3", "generic_abs_ultimaker3_AA_0.4", etc.
#
class MaterialGroup:
- __slots__ = ("name", "root_material_node", "derived_material_node_list")
+ __slots__ = ("name", "is_read_only", "root_material_node", "derived_material_node_list")
- def __init__(self, name: str):
+ def __init__(self, name: str, root_material_node: MaterialNode):
self.name = name
- self.root_material_node = None
- self.derived_material_node_list = []
+ self.is_read_only = False
+ self.root_material_node = root_material_node
+ self.derived_material_node_list = [] #type: List[MaterialNode]
def __str__(self) -> str:
return "%s[%s]" % (self.__class__.__name__, self.name)
diff --git a/cura/Machines/MaterialManager.py b/cura/Machines/MaterialManager.py
index 98e4f67f82..24c7ccb8c0 100644
--- a/cura/Machines/MaterialManager.py
+++ b/cura/Machines/MaterialManager.py
@@ -72,29 +72,28 @@ class MaterialManager(QObject):
def initialize(self):
# Find all materials and put them in a matrix for quick search.
- material_metadata_list = self._container_registry.findContainersMetadata(type = "material")
+ material_metadatas = {metadata["id"]: metadata for metadata in self._container_registry.findContainersMetadata(type = "material")}
self._material_group_map = dict()
# Map #1
# root_material_id -> MaterialGroup
- for material_metadata in material_metadata_list:
- material_id = material_metadata["id"]
+ for material_id, material_metadata in material_metadatas.items():
# We don't store empty material in the lookup tables
if material_id == "empty_material":
continue
root_material_id = material_metadata.get("base_file")
if root_material_id not in self._material_group_map:
- self._material_group_map[root_material_id] = MaterialGroup(root_material_id)
+ self._material_group_map[root_material_id] = MaterialGroup(root_material_id, MaterialNode(material_metadatas[root_material_id]))
+ self._material_group_map[root_material_id].is_read_only = self._container_registry.isReadOnly(root_material_id)
group = self._material_group_map[root_material_id]
- # We only add root materials here
- if material_id == root_material_id:
- group.root_material_node = MaterialNode(material_metadata)
- else:
+ #Store this material in the group of the appropriate root material.
+ if material_id != root_material_id:
new_node = MaterialNode(material_metadata)
group.derived_material_node_list.append(new_node)
+
# Order this map alphabetically so it's easier to navigate in a debugger
self._material_group_map = OrderedDict(sorted(self._material_group_map.items(), key = lambda x: x[0]))
@@ -108,6 +107,7 @@ class MaterialManager(QObject):
# Map #2
# Lookup table for material type -> fallback material metadata, only for read-only materials
grouped_by_type_dict = dict()
+ material_types_without_fallback = set()
for root_material_id, material_node in self._material_group_map.items():
if not self._container_registry.isReadOnly(root_material_id):
continue
@@ -115,6 +115,7 @@ class MaterialManager(QObject):
if material_type not in grouped_by_type_dict:
grouped_by_type_dict[material_type] = {"generic": None,
"others": []}
+ material_types_without_fallback.add(material_type)
brand = material_node.root_material_node.metadata["brand"]
if brand.lower() == "generic":
to_add = True
@@ -124,6 +125,10 @@ class MaterialManager(QObject):
to_add = False # don't add if it's not the default diameter
if to_add:
grouped_by_type_dict[material_type] = material_node.root_material_node.metadata
+ material_types_without_fallback.remove(material_type)
+ # Remove the materials that have no fallback materials
+ for material_type in material_types_without_fallback:
+ del grouped_by_type_dict[material_type]
self._fallback_materials_map = grouped_by_type_dict
# Map #3
@@ -172,7 +177,7 @@ class MaterialManager(QObject):
# "machine" -> "variant_name" -> "root material ID" -> specific material InstanceContainer
# Construct the "machine" -> "variant" -> "root material ID" -> specific material InstanceContainer
self._diameter_machine_variant_material_map = dict()
- for material_metadata in material_metadata_list:
+ for material_metadata in material_metadatas.values():
# We don't store empty material in the lookup tables
if material_metadata["id"] == "empty_material":
continue
@@ -210,6 +215,7 @@ class MaterialManager(QObject):
self.materialsUpdated.emit()
def _updateMaps(self):
+ Logger.log("i", "Updating material lookup data ...")
self.initialize()
def _onContainerMetadataChanged(self, container):
@@ -325,6 +331,35 @@ class MaterialManager(QObject):
return material_node
+ #
+ # Gets MaterialNode for the given extruder and machine with the given material type.
+ # Returns None if:
+ # 1. the given machine doesn't have materials;
+ # 2. cannot find any material InstanceContainers with the given settings.
+ #
+ def getMaterialNodeByType(self, global_stack: "GlobalStack", extruder_variant_name: str, material_guid: str) -> Optional["MaterialNode"]:
+ node = None
+ machine_definition = global_stack.definition
+ if parseBool(machine_definition.getMetaDataEntry("has_materials", False)):
+ material_diameter = machine_definition.getProperty("material_diameter", "value")
+ if isinstance(material_diameter, SettingFunction):
+ material_diameter = material_diameter(global_stack)
+
+ # Look at the guid to material dictionary
+ root_material_id = None
+ for material_group in self._guid_material_groups_map[material_guid]:
+ if material_group.is_read_only:
+ root_material_id = material_group.root_material_node.metadata["id"]
+ break
+
+ if not root_material_id:
+ Logger.log("i", "Cannot find materials with guid [%s] ", material_guid)
+ return None
+
+ node = self.getMaterialNode(machine_definition.getId(), extruder_variant_name,
+ material_diameter, root_material_id)
+ return node
+
#
# Used by QualityManager. Built-in quality profiles may be based on generic material IDs such as "generic_pla".
# For materials such as ultimaker_pla_orange, no quality profiles may be found, so we should fall back to use
@@ -349,10 +384,10 @@ class MaterialManager(QObject):
else:
return None
- def getDefaultMaterial(self, global_stack: "GlobalStack", extruder_variant_name: str) -> Optional["MaterialNode"]:
+ def getDefaultMaterial(self, global_stack: "GlobalStack", extruder_variant_name: Optional[str]) -> Optional["MaterialNode"]:
node = None
machine_definition = global_stack.definition
- if parseBool(machine_definition.getMetaDataEntry("has_materials", False)):
+ if parseBool(global_stack.getMetaDataEntry("has_materials", False)):
material_diameter = machine_definition.getProperty("material_diameter", "value")
if isinstance(material_diameter, SettingFunction):
material_diameter = material_diameter(global_stack)
@@ -363,6 +398,16 @@ class MaterialManager(QObject):
material_diameter, root_material_id)
return node
+ def removeMaterialByRootId(self, root_material_id: str):
+ material_group = self.getMaterialGroup(root_material_id)
+ if not material_group:
+ Logger.log("i", "Unable to remove the material with id %s, because it doesn't exist.", root_material_id)
+ return
+
+ nodes_to_remove = [material_group.root_material_node] + material_group.derived_material_node_list
+ for node in nodes_to_remove:
+ self._container_registry.removeContainer(node.metadata["id"])
+
#
# Methods for GUI
#
@@ -386,14 +431,7 @@ class MaterialManager(QObject):
@pyqtSlot("QVariant")
def removeMaterial(self, material_node: "MaterialNode"):
root_material_id = material_node.metadata["base_file"]
- material_group = self.getMaterialGroup(root_material_id)
- if not material_group:
- Logger.log("d", "Unable to remove the material with id %s, because it doesn't exist.", root_material_id)
- return
-
- nodes_to_remove = [material_group.root_material_node] + material_group.derived_material_node_list
- for node in nodes_to_remove:
- self._container_registry.removeContainer(node.metadata["id"])
+ self.removeMaterialByRootId(root_material_id)
#
# Creates a duplicate of a material, which has the same GUID and base_file metadata.
@@ -460,8 +498,10 @@ class MaterialManager(QObject):
# Ensure all settings are saved.
self._application.saveSettings()
- global_stack = self._application.getGlobalContainerStack()
- approximate_diameter = str(round(global_stack.getProperty("material_diameter", "value")))
+ machine_manager = self._application.getMachineManager()
+ extruder_stack = machine_manager.activeStack
+
+ approximate_diameter = str(extruder_stack.approximateMaterialDiameter)
root_material_id = "generic_pla"
root_material_id = self.getRootMaterialIDForDiameter(root_material_id, approximate_diameter)
material_group = self.getMaterialGroup(root_material_id)
diff --git a/cura/Machines/Models/BaseMaterialsModel.py b/cura/Machines/Models/BaseMaterialsModel.py
index de0c68d60a..0a1337feeb 100644
--- a/cura/Machines/Models/BaseMaterialsModel.py
+++ b/cura/Machines/Models/BaseMaterialsModel.py
@@ -3,6 +3,7 @@
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty
+from UM.Application import Application
from UM.Qt.ListModel import ListModel
@@ -25,6 +26,8 @@ class BaseMaterialsModel(ListModel):
def __init__(self, parent = None):
super().__init__(parent)
+ self._application = Application.getInstance()
+ self._machine_manager = self._application.getMachineManager()
self.addRoleName(self.RootMaterialIdRole, "root_material_id")
self.addRoleName(self.IdRole, "id")
@@ -35,12 +38,31 @@ class BaseMaterialsModel(ListModel):
self.addRoleName(self.ContainerNodeRole, "container_node")
self._extruder_position = 0
+ self._extruder_stack = None
+
+ def _updateExtruderStack(self):
+ global_stack = self._machine_manager.activeMachine
+ if global_stack is None:
+ return
+
+ if self._extruder_stack is not None:
+ self._extruder_stack.pyqtContainersChanged.disconnect(self._update)
+ self._extruder_stack = global_stack.extruders.get(str(self._extruder_position))
+ if self._extruder_stack is not None:
+ self._extruder_stack.pyqtContainersChanged.connect(self._update)
def setExtruderPosition(self, position: int):
if self._extruder_position != position:
self._extruder_position = position
+ self._updateExtruderStack()
self.extruderPositionChanged.emit()
@pyqtProperty(int, fset = setExtruderPosition, notify = extruderPositionChanged)
def extruderPosition(self) -> int:
- return self._extruder_positoin
+ return self._extruder_position
+
+ #
+ # This is an abstract method that needs to be implemented by
+ #
+ def _update(self):
+ pass
diff --git a/cura/Machines/Models/BrandMaterialsModel.py b/cura/Machines/Models/BrandMaterialsModel.py
index 6628d924f1..f6c9a14632 100644
--- a/cura/Machines/Models/BrandMaterialsModel.py
+++ b/cura/Machines/Models/BrandMaterialsModel.py
@@ -4,8 +4,8 @@
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty
from UM.Qt.ListModel import ListModel
-
-from .BaseMaterialsModel import BaseMaterialsModel
+from UM.Logger import Logger
+from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel
#
@@ -53,10 +53,8 @@ class BrandMaterialsModel(ListModel):
self._extruder_manager = CuraApplication.getInstance().getExtruderManager()
self._material_manager = CuraApplication.getInstance().getMaterialManager()
- self._machine_manager.globalContainerChanged.connect(self._update)
- self._extruder_manager.activeExtruderChanged.connect(self._update)
- self._material_manager.materialsUpdated.connect(self._update)
-
+ self._machine_manager.activeStackChanged.connect(self._update) #Update when switching machines.
+ self._material_manager.materialsUpdated.connect(self._update) #Update when the list of materials changes.
self._update()
def setExtruderPosition(self, position: int):
@@ -69,6 +67,7 @@ class BrandMaterialsModel(ListModel):
return self._extruder_position
def _update(self):
+ Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
global_stack = self._machine_manager.activeMachine
if global_stack is None:
self.setItems([])
@@ -120,12 +119,19 @@ class BrandMaterialsModel(ListModel):
material_type_item = {"name": material_type,
"colors": BaseMaterialsModel(self)}
material_type_item["colors"].clear()
+
+ # Sort materials by name
+ material_list = sorted(material_list, key = lambda x: x["name"].upper())
material_type_item["colors"].setItems(material_list)
material_type_item_list.append(material_type_item)
+ # Sort material type by name
+ material_type_item_list = sorted(material_type_item_list, key = lambda x: x["name"].upper())
brand_item["materials"].setItems(material_type_item_list)
brand_item_list.append(brand_item)
+ # Sort brand by name
+ brand_item_list = sorted(brand_item_list, key = lambda x: x["name"].upper())
self.setItems(brand_item_list)
diff --git a/cura/Machines/Models/BuildPlateModel.py b/cura/Machines/Models/BuildPlateModel.py
index 1cb94216a6..e1b4f40d8e 100644
--- a/cura/Machines/Models/BuildPlateModel.py
+++ b/cura/Machines/Models/BuildPlateModel.py
@@ -4,6 +4,7 @@
from PyQt5.QtCore import Qt
from UM.Application import Application
+from UM.Logger import Logger
from UM.Qt.ListModel import ListModel
from UM.Util import parseBool
@@ -29,6 +30,7 @@ class BuildPlateModel(ListModel):
self._update()
def _update(self):
+ Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
global_stack = self._machine_manager._global_container_stack
if not global_stack:
self.setItems([])
diff --git a/cura/Machines/Models/CustomQualityProfilesDropDownMenuModel.py b/cura/Machines/Models/CustomQualityProfilesDropDownMenuModel.py
index 0d297379cd..dcade8cb0d 100644
--- a/cura/Machines/Models/CustomQualityProfilesDropDownMenuModel.py
+++ b/cura/Machines/Models/CustomQualityProfilesDropDownMenuModel.py
@@ -12,7 +12,7 @@ from cura.Machines.Models.QualityProfilesDropDownMenuModel import QualityProfile
class CustomQualityProfilesDropDownMenuModel(QualityProfilesDropDownMenuModel):
def _update(self):
- Logger.log("d", "Updating %s ...", self.__class__.__name__)
+ Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
active_global_stack = self._machine_manager.activeMachine
if active_global_stack is None:
@@ -23,7 +23,7 @@ class CustomQualityProfilesDropDownMenuModel(QualityProfilesDropDownMenuModel):
quality_changes_group_dict = self._quality_manager.getQualityChangesGroups(active_global_stack)
item_list = []
- for key in sorted(quality_changes_group_dict):
+ for key in sorted(quality_changes_group_dict, key = lambda name: name.upper()):
quality_changes_group = quality_changes_group_dict[key]
item = {"name": quality_changes_group.name,
diff --git a/cura/Machines/Models/GenericMaterialsModel.py b/cura/Machines/Models/GenericMaterialsModel.py
index d20fc05b6e..2fac919f3e 100644
--- a/cura/Machines/Models/GenericMaterialsModel.py
+++ b/cura/Machines/Models/GenericMaterialsModel.py
@@ -1,7 +1,8 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from .BaseMaterialsModel import BaseMaterialsModel
+from UM.Logger import Logger
+from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel
class GenericMaterialsModel(BaseMaterialsModel):
@@ -14,13 +15,13 @@ class GenericMaterialsModel(BaseMaterialsModel):
self._extruder_manager = CuraApplication.getInstance().getExtruderManager()
self._material_manager = CuraApplication.getInstance().getMaterialManager()
- self._machine_manager.globalContainerChanged.connect(self._update)
- self._extruder_manager.activeExtruderChanged.connect(self._update)
- self._material_manager.materialsUpdated.connect(self._update)
-
+ self._machine_manager.activeStackChanged.connect(self._update) #Update when switching machines.
+ self._material_manager.materialsUpdated.connect(self._update) #Update when the list of materials changes.
self._update()
def _update(self):
+ Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
+
global_stack = self._machine_manager.activeMachine
if global_stack is None:
self.setItems([])
@@ -55,6 +56,6 @@ class GenericMaterialsModel(BaseMaterialsModel):
item_list.append(item)
# Sort the item list by material name alphabetically
- item_list = sorted(item_list, key = lambda d: d["name"])
+ item_list = sorted(item_list, key = lambda d: d["name"].upper())
self.setItems(item_list)
diff --git a/cura/Machines/Models/MachineManagementModel.py b/cura/Machines/Models/MachineManagementModel.py
new file mode 100644
index 0000000000..7dc51f07f7
--- /dev/null
+++ b/cura/Machines/Models/MachineManagementModel.py
@@ -0,0 +1,82 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from UM.Qt.ListModel import ListModel
+
+from PyQt5.QtCore import Qt
+
+from UM.Settings.ContainerRegistry import ContainerRegistry
+from UM.Settings.ContainerStack import ContainerStack
+
+from UM.i18n import i18nCatalog
+catalog = i18nCatalog("cura")
+
+
+#
+# This the QML model for the quality management page.
+#
+class MachineManagementModel(ListModel):
+ NameRole = Qt.UserRole + 1
+ IdRole = Qt.UserRole + 2
+ MetaDataRole = Qt.UserRole + 3
+ GroupRole = Qt.UserRole + 4
+
+ def __init__(self, parent = None):
+ super().__init__(parent)
+ self.addRoleName(self.NameRole, "name")
+ self.addRoleName(self.IdRole, "id")
+ self.addRoleName(self.MetaDataRole, "metadata")
+ self.addRoleName(self.GroupRole, "group")
+ self._local_container_stacks = []
+ self._network_container_stacks = []
+
+ # Listen to changes
+ ContainerRegistry.getInstance().containerAdded.connect(self._onContainerChanged)
+ ContainerRegistry.getInstance().containerMetaDataChanged.connect(self._onContainerChanged)
+ ContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChanged)
+ self._filter_dict = {}
+ self._update()
+
+ ## Handler for container added/removed events from registry
+ def _onContainerChanged(self, container):
+ # We only need to update when the added / removed container is a stack.
+ if isinstance(container, ContainerStack) and container.getMetaDataEntry("type") == "machine":
+ self._update()
+
+ ## Private convenience function to reset & repopulate the model.
+ def _update(self):
+ items = []
+
+ # Get first the network enabled printers
+ network_filter_printers = {"type": "machine",
+ "um_network_key": "*",
+ "hidden": "False"}
+ self._network_container_stacks = ContainerRegistry.getInstance().findContainerStacks(**network_filter_printers)
+ self._network_container_stacks.sort(key = lambda i: i.getMetaDataEntry("connect_group_name"))
+
+ for container in self._network_container_stacks:
+ metadata = container.getMetaData().copy()
+ if container.getBottom():
+ metadata["definition_name"] = container.getBottom().getName()
+
+ items.append({"name": metadata["connect_group_name"],
+ "id": container.getId(),
+ "metadata": metadata,
+ "group": catalog.i18nc("@info:title", "Network enabled printers")})
+
+ # Get now the local printers
+ local_filter_printers = {"type": "machine", "um_network_key": None}
+ self._local_container_stacks = ContainerRegistry.getInstance().findContainerStacks(**local_filter_printers)
+ self._local_container_stacks.sort(key = lambda i: i.getName())
+
+ for container in self._local_container_stacks:
+ metadata = container.getMetaData().copy()
+ if container.getBottom():
+ metadata["definition_name"] = container.getBottom().getName()
+
+ items.append({"name": container.getName(),
+ "id": container.getId(),
+ "metadata": metadata,
+ "group": catalog.i18nc("@info:title", "Local printers")})
+
+ self.setItems(items)
diff --git a/cura/Machines/Models/MaterialManagementModel.py b/cura/Machines/Models/MaterialManagementModel.py
index b250232282..46e9cb887a 100644
--- a/cura/Machines/Models/MaterialManagementModel.py
+++ b/cura/Machines/Models/MaterialManagementModel.py
@@ -1,8 +1,9 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from PyQt5.QtCore import Qt, pyqtProperty
+from PyQt5.QtCore import Qt
+from UM.Logger import Logger
from UM.Qt.ListModel import ListModel
@@ -60,6 +61,8 @@ class MaterialManagementModel(ListModel):
self._update()
def _update(self):
+ Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
+
global_stack = self._machine_manager.activeMachine
if global_stack is None:
self.setItems([])
@@ -97,5 +100,5 @@ class MaterialManagementModel(ListModel):
material_list.append(item)
- material_list = sorted(material_list, key = lambda k: (k["brand"].lower(), k["name"]))
+ material_list = sorted(material_list, key = lambda k: (k["brand"].upper(), k["name"].upper()))
self.setItems(material_list)
diff --git a/cura/Machines/Models/MultiBuildPlateModel.py b/cura/Machines/Models/MultiBuildPlateModel.py
index f0f4997014..958e93837a 100644
--- a/cura/Machines/Models/MultiBuildPlateModel.py
+++ b/cura/Machines/Models/MultiBuildPlateModel.py
@@ -1,7 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from PyQt5.QtCore import pyqtSignal, pyqtProperty
+from PyQt5.QtCore import QTimer, pyqtSignal, pyqtProperty
from UM.Application import Application
from UM.Scene.Selection import Selection
@@ -21,8 +21,13 @@ class MultiBuildPlateModel(ListModel):
def __init__(self, parent = None):
super().__init__(parent)
+ self._update_timer = QTimer()
+ self._update_timer.setInterval(100)
+ self._update_timer.setSingleShot(True)
+ self._update_timer.timeout.connect(self._updateSelectedObjectBuildPlateNumbers)
+
self._application = Application.getInstance()
- self._application.getController().getScene().sceneChanged.connect(self._updateSelectedObjectBuildPlateNumbers)
+ self._application.getController().getScene().sceneChanged.connect(self._updateSelectedObjectBuildPlateNumbersDelayed)
Selection.selectionChanged.connect(self._updateSelectedObjectBuildPlateNumbers)
self._max_build_plate = 1 # default
@@ -45,6 +50,9 @@ class MultiBuildPlateModel(ListModel):
def activeBuildPlate(self):
return self._active_build_plate
+ def _updateSelectedObjectBuildPlateNumbersDelayed(self, *args):
+ self._update_timer.start()
+
def _updateSelectedObjectBuildPlateNumbers(self, *args):
result = set()
for node in Selection.getAllSelectedObjects():
diff --git a/cura/Machines/Models/NozzleModel.py b/cura/Machines/Models/NozzleModel.py
index 19d4a800c8..0879998b7d 100644
--- a/cura/Machines/Models/NozzleModel.py
+++ b/cura/Machines/Models/NozzleModel.py
@@ -4,6 +4,7 @@
from PyQt5.QtCore import Qt
from UM.Application import Application
+from UM.Logger import Logger
from UM.Qt.ListModel import ListModel
from UM.Util import parseBool
@@ -20,32 +21,36 @@ class NozzleModel(ListModel):
self.addRoleName(self.HotendNameRole, "hotend_name")
self.addRoleName(self.ContainerNodeRole, "container_node")
- Application.getInstance().globalContainerStackChanged.connect(self._update)
- Application.getInstance().getMachineManager().activeVariantChanged.connect(self._update)
- Application.getInstance().getMachineManager().activeStackChanged.connect(self._update)
- Application.getInstance().getMachineManager().activeMaterialChanged.connect(self._update)
+ self._application = Application.getInstance()
+ self._machine_manager = self._application.getMachineManager()
+ self._variant_manager = self._application.getVariantManager()
+
+ self._machine_manager.globalContainerChanged.connect(self._update)
+ self._update()
def _update(self):
+ Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
+
self.items.clear()
- variant_manager = Application.getInstance()._variant_manager
- active_global_stack = Application.getInstance().getMachineManager()._global_container_stack
- if active_global_stack is None:
+ global_stack = self._machine_manager.activeMachine
+ if global_stack is None:
self.setItems([])
return
- has_variants = parseBool(active_global_stack.getMetaDataEntry("has_variants", False))
+ has_variants = parseBool(global_stack.getMetaDataEntry("has_variants", False))
if not has_variants:
self.setItems([])
return
- variant_node_dict = variant_manager.getVariantNodes(active_global_stack)
+ from cura.Machines.VariantManager import VariantType
+ variant_node_dict = self._variant_manager.getVariantNodes(global_stack, VariantType.NOZZLE)
if not variant_node_dict:
self.setItems([])
return
item_list = []
- for hotend_name, container_node in sorted(variant_node_dict.items(), key = lambda i: i[0]):
+ for hotend_name, container_node in sorted(variant_node_dict.items(), key = lambda i: i[0].upper()):
item = {"id": hotend_name,
"hotend_name": hotend_name,
"container_node": container_node
diff --git a/cura/Machines/Models/QualityManagementModel.py b/cura/Machines/Models/QualityManagementModel.py
index e089f92329..4d2b551805 100644
--- a/cura/Machines/Models/QualityManagementModel.py
+++ b/cura/Machines/Models/QualityManagementModel.py
@@ -4,7 +4,7 @@
from PyQt5.QtCore import Qt, pyqtSlot
from UM.Qt.ListModel import ListModel
-
+from UM.Logger import Logger
#
# This the QML model for the quality management page.
@@ -35,6 +35,8 @@ class QualityManagementModel(ListModel):
self._update()
def _update(self):
+ Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
+
global_stack = self._machine_manager.activeMachine
quality_group_dict = self._quality_manager.getQualityGroups(global_stack)
@@ -59,7 +61,7 @@ class QualityManagementModel(ListModel):
"quality_changes_group": None}
item_list.append(item)
# Sort by quality names
- item_list = sorted(item_list, key = lambda x: x["name"])
+ item_list = sorted(item_list, key = lambda x: x["name"].upper())
# Create quality_changes group items
quality_changes_item_list = []
@@ -74,7 +76,7 @@ class QualityManagementModel(ListModel):
quality_changes_item_list.append(item)
# Sort quality_changes items by names and append to the item list
- quality_changes_item_list = sorted(quality_changes_item_list, key = lambda x: x["name"])
+ quality_changes_item_list = sorted(quality_changes_item_list, key = lambda x: x["name"].upper())
item_list += quality_changes_item_list
self.setItems(item_list)
diff --git a/cura/Machines/Models/QualityProfilesDropDownMenuModel.py b/cura/Machines/Models/QualityProfilesDropDownMenuModel.py
index fd919639c3..d8c4b668cf 100644
--- a/cura/Machines/Models/QualityProfilesDropDownMenuModel.py
+++ b/cura/Machines/Models/QualityProfilesDropDownMenuModel.py
@@ -29,7 +29,7 @@ class QualityProfilesDropDownMenuModel(ListModel):
self.addRoleName(self.QualityTypeRole, "quality_type")
self.addRoleName(self.LayerHeightRole, "layer_height")
self.addRoleName(self.LayerHeightUnitRole, "layer_height_unit")
- self.addRoleName(self.AvailableRole, "available")
+ self.addRoleName(self.AvailableRole, "available") #Whether the quality profile is available in our current nozzle + material.
self.addRoleName(self.QualityGroupRole, "quality_group")
self.addRoleName(self.QualityChangesGroupRole, "quality_changes_group")
@@ -39,6 +39,7 @@ class QualityProfilesDropDownMenuModel(ListModel):
self._application.globalContainerStackChanged.connect(self._update)
self._machine_manager.activeQualityGroupChanged.connect(self._update)
+ self._machine_manager.extruderChanged.connect(self._update)
self._quality_manager.qualitiesUpdated.connect(self._update)
self._layer_height_unit = "" # This is cached
@@ -46,7 +47,7 @@ class QualityProfilesDropDownMenuModel(ListModel):
self._update()
def _update(self):
- Logger.log("d", "Updating quality profile model ...")
+ Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
global_stack = self._machine_manager.activeMachine
if global_stack is None:
diff --git a/cura/Machines/Models/QualitySettingsModel.py b/cura/Machines/Models/QualitySettingsModel.py
index 38b7ec28e4..b38f6f65c8 100644
--- a/cura/Machines/Models/QualitySettingsModel.py
+++ b/cura/Machines/Models/QualitySettingsModel.py
@@ -21,6 +21,8 @@ class QualitySettingsModel(ListModel):
UserValueRole = Qt.UserRole + 6
CategoryRole = Qt.UserRole + 7
+ GLOBAL_STACK_POSITION = -1
+
def __init__(self, parent = None):
super().__init__(parent = parent)
@@ -36,8 +38,7 @@ class QualitySettingsModel(ListModel):
self._application = Application.getInstance()
self._quality_manager = self._application.getQualityManager()
- self._selected_position = "" # empty string means GlobalStack
- # strings such as "0", "1", etc. mean extruder positions
+ self._selected_position = self.GLOBAL_STACK_POSITION #Must be either GLOBAL_STACK_POSITION or an extruder position (0, 1, etc.)
self._selected_quality_item = None # The selected quality in the quality management page
self._i18n_catalog = None
@@ -54,7 +55,7 @@ class QualitySettingsModel(ListModel):
self.selectedPositionChanged.emit()
self._update()
- @pyqtProperty(str, fset = setSelectedPosition, notify = selectedPositionChanged)
+ @pyqtProperty(int, fset = setSelectedPosition, notify = selectedPositionChanged)
def selectedPosition(self):
return self._selected_position
@@ -69,7 +70,9 @@ class QualitySettingsModel(ListModel):
return self._selected_quality_item
def _update(self):
- if self._selected_quality_item is None:
+ Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
+
+ if not self._selected_quality_item:
self.setItems([])
return
@@ -81,24 +84,26 @@ class QualitySettingsModel(ListModel):
quality_group = self._selected_quality_item["quality_group"]
quality_changes_group = self._selected_quality_item["quality_changes_group"]
- if self._selected_position == "":
+ if self._selected_position == self.GLOBAL_STACK_POSITION:
quality_node = quality_group.node_for_global
else:
- quality_node = quality_group.nodes_for_extruders.get(self._selected_position)
+ quality_node = quality_group.nodes_for_extruders.get(str(self._selected_position))
settings_keys = quality_group.getAllKeys()
- quality_containers = [quality_node.getContainer()]
+ quality_containers = []
+ if quality_node is not None:
+ quality_containers.append(quality_node.getContainer())
# Here, if the user has selected a quality changes, then "quality_changes_group" will not be None, and we fetch
# the settings in that quality_changes_group.
if quality_changes_group is not None:
- if self._selected_position == "":
+ if self._selected_position == self.GLOBAL_STACK_POSITION:
quality_changes_node = quality_changes_group.node_for_global
else:
- quality_changes_node = quality_changes_group.nodes_for_extruders.get(self._selected_position)
+ quality_changes_node = quality_changes_group.nodes_for_extruders.get(str(self._selected_position))
if quality_changes_node is not None: # it can be None if number of extruders are changed during runtime
try:
quality_containers.insert(0, quality_changes_node.getContainer())
- except:
+ except RuntimeError:
# FIXME: This is to prevent incomplete update of QualityManager
Logger.logException("d", "Failed to get container for quality changes node %s", quality_changes_node)
return
@@ -125,7 +130,7 @@ class QualitySettingsModel(ListModel):
profile_value = new_value
# Global tab should use resolve (if there is one)
- if self._selected_position == "":
+ if self._selected_position == self.GLOBAL_STACK_POSITION:
resolve_value = global_container_stack.getProperty(definition.key, "resolve")
if resolve_value is not None and definition.key in settings_keys:
profile_value = resolve_value
@@ -133,10 +138,10 @@ class QualitySettingsModel(ListModel):
if profile_value is not None:
break
- if not self._selected_position:
+ if self._selected_position == self.GLOBAL_STACK_POSITION:
user_value = global_container_stack.userChanges.getProperty(definition.key, "value")
else:
- extruder_stack = global_container_stack.extruders[self._selected_position]
+ extruder_stack = global_container_stack.extruders[str(self._selected_position)]
user_value = extruder_stack.userChanges.getProperty(definition.key, "value")
if profile_value is None and user_value is None:
diff --git a/cura/Machines/Models/SettingVisibilityPresetsModel.py b/cura/Machines/Models/SettingVisibilityPresetsModel.py
new file mode 100644
index 0000000000..8880ac5ce1
--- /dev/null
+++ b/cura/Machines/Models/SettingVisibilityPresetsModel.py
@@ -0,0 +1,182 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from typing import Optional
+import os
+import urllib.parse
+from configparser import ConfigParser
+
+from PyQt5.QtCore import pyqtProperty, Qt, pyqtSignal, pyqtSlot
+
+from UM.Logger import Logger
+from UM.Qt.ListModel import ListModel
+from UM.Preferences import Preferences
+from UM.Resources import Resources
+from UM.MimeTypeDatabase import MimeTypeDatabase, MimeTypeNotFoundError
+
+from UM.i18n import i18nCatalog
+catalog = i18nCatalog("cura")
+
+
+class SettingVisibilityPresetsModel(ListModel):
+ IdRole = Qt.UserRole + 1
+ NameRole = Qt.UserRole + 2
+ SettingsRole = Qt.UserRole + 3
+
+ def __init__(self, parent = None):
+ super().__init__(parent)
+ self.addRoleName(self.IdRole, "id")
+ self.addRoleName(self.NameRole, "name")
+ self.addRoleName(self.SettingsRole, "settings")
+
+ self._populate()
+ basic_item = self.items[1]
+ basic_visibile_settings = ";".join(basic_item["settings"])
+
+ self._preferences = Preferences.getInstance()
+ # Preference to store which preset is currently selected
+ self._preferences.addPreference("cura/active_setting_visibility_preset", "basic")
+ # Preference that stores the "custom" set so it can always be restored (even after a restart)
+ self._preferences.addPreference("cura/custom_visible_settings", basic_visibile_settings)
+ self._preferences.preferenceChanged.connect(self._onPreferencesChanged)
+
+ self._active_preset_item = self._getItem(self._preferences.getValue("cura/active_setting_visibility_preset"))
+ # Initialize visible settings if it is not done yet
+ visible_settings = self._preferences.getValue("general/visible_settings")
+ if not visible_settings:
+ self._preferences.setValue("general/visible_settings", ";".join(self._active_preset_item["settings"]))
+ else:
+ self._onPreferencesChanged("general/visible_settings")
+
+ self.activePresetChanged.emit()
+
+ def _getItem(self, item_id: str) -> Optional[dict]:
+ result = None
+ for item in self.items:
+ if item["id"] == item_id:
+ result = item
+ break
+ return result
+
+ def _populate(self):
+ from cura.CuraApplication import CuraApplication
+ items = []
+ for file_path in Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.SettingVisibilityPreset):
+ try:
+ mime_type = MimeTypeDatabase.getMimeTypeForFile(file_path)
+ except MimeTypeNotFoundError:
+ Logger.log("e", "Could not determine mime type of file %s", file_path)
+ continue
+
+ item_id = urllib.parse.unquote_plus(mime_type.stripExtension(os.path.basename(file_path)))
+ if not os.path.isfile(file_path):
+ Logger.log("e", "[%s] is not a file", file_path)
+ continue
+
+ parser = ConfigParser(allow_no_value = True) # accept options without any value,
+ try:
+ parser.read([file_path])
+ if not parser.has_option("general", "name") or not parser.has_option("general", "weight"):
+ continue
+
+ settings = []
+ for section in parser.sections():
+ if section == 'general':
+ continue
+
+ settings.append(section)
+ for option in parser[section].keys():
+ settings.append(option)
+
+ items.append({
+ "id": item_id,
+ "name": catalog.i18nc("@action:inmenu", parser["general"]["name"]),
+ "weight": parser["general"]["weight"],
+ "settings": settings,
+ })
+
+ except Exception:
+ Logger.logException("e", "Failed to load setting preset %s", file_path)
+
+ items.sort(key = lambda k: (int(k["weight"]), k["id"]))
+ # Put "custom" at the top
+ items.insert(0, {"id": "custom",
+ "name": "Custom selection",
+ "weight": -100,
+ "settings": []})
+
+ self.setItems(items)
+
+ @pyqtSlot(str)
+ def setActivePreset(self, preset_id: str):
+ if preset_id == self._active_preset_item["id"]:
+ Logger.log("d", "Same setting visibility preset [%s] selected, do nothing.", preset_id)
+ return
+
+ preset_item = None
+ for item in self.items:
+ if item["id"] == preset_id:
+ preset_item = item
+ break
+ if preset_item is None:
+ Logger.log("w", "Tried to set active preset to unknown id [%s]", preset_id)
+ return
+
+ need_to_save_to_custom = self._active_preset_item["id"] == "custom" and preset_id != "custom"
+ if need_to_save_to_custom:
+ # Save the current visibility settings to custom
+ current_visibility_string = self._preferences.getValue("general/visible_settings")
+ if current_visibility_string:
+ self._preferences.setValue("cura/custom_visible_settings", current_visibility_string)
+
+ new_visibility_string = ";".join(preset_item["settings"])
+ if preset_id == "custom":
+ # Get settings from the stored custom data
+ new_visibility_string = self._preferences.getValue("cura/custom_visible_settings")
+ if new_visibility_string is None:
+ new_visibility_string = self._preferences.getValue("general/visible_settings")
+ self._preferences.setValue("general/visible_settings", new_visibility_string)
+
+ self._preferences.setValue("cura/active_setting_visibility_preset", preset_id)
+ self._active_preset_item = preset_item
+ self.activePresetChanged.emit()
+
+ activePresetChanged = pyqtSignal()
+
+ @pyqtProperty(str, notify = activePresetChanged)
+ def activePreset(self) -> str:
+ return self._active_preset_item["id"]
+
+ def _onPreferencesChanged(self, name: str):
+ if name != "general/visible_settings":
+ return
+
+ # Find the preset that matches with the current visible settings setup
+ visibility_string = self._preferences.getValue("general/visible_settings")
+ if not visibility_string:
+ return
+
+ visibility_set = set(visibility_string.split(";"))
+ matching_preset_item = None
+ for item in self.items:
+ if item["id"] == "custom":
+ continue
+ if set(item["settings"]) == visibility_set:
+ matching_preset_item = item
+ break
+
+ item_to_set = self._active_preset_item
+ if matching_preset_item is None:
+ # The new visibility setup is "custom" should be custom
+ if self._active_preset_item["id"] == "custom":
+ # We are already in custom, just save the settings
+ self._preferences.setValue("cura/custom_visible_settings", visibility_string)
+ else:
+ item_to_set = self.items[0] # 0 is custom
+ else:
+ item_to_set = matching_preset_item
+
+ if self._active_preset_item is None or self._active_preset_item["id"] != item_to_set["id"]:
+ self._active_preset_item = item_to_set
+ self._preferences.setValue("cura/active_setting_visibility_preset", self._active_preset_item["id"])
+ self.activePresetChanged.emit()
diff --git a/cura/Machines/QualityChangesGroup.py b/cura/Machines/QualityChangesGroup.py
index f8de3d2011..ad320a7006 100644
--- a/cura/Machines/QualityChangesGroup.py
+++ b/cura/Machines/QualityChangesGroup.py
@@ -7,47 +7,21 @@ from .QualityGroup import QualityGroup
class QualityChangesGroup(QualityGroup):
-
def __init__(self, name: str, quality_type: str, parent = None):
super().__init__(name, quality_type, parent)
self._container_registry = Application.getInstance().getContainerRegistry()
def addNode(self, node: "QualityNode"):
- # TODO: in 3.2 and earlier, a quality_changes container may have a field called "extruder" which contains the
- # extruder definition ID it belongs to. But, in fact, we only need to know the following things:
- # 1. which machine a custom profile is suitable for,
- # 2. if this profile is for the GlobalStack,
- # 3. if this profile is for an ExtruderStack and which one (the position).
- #
- # So, it is preferred to have a field like this:
- # extruder_position = 1
- # instead of this:
- # extruder = custom_extruder_1
- #
- # An upgrade needs to be done if we want to do it this way. Before that, we use the extruder's definition
- # to figure out its position.
- #
- extruder_definition_id = node.metadata.get("extruder")
- if extruder_definition_id:
- metadata_list = self._container_registry.findDefinitionContainersMetadata(id = extruder_definition_id)
- if not metadata_list:
- raise RuntimeError("%s cannot get metadata for extruder definition [%s]" %
- (self, extruder_definition_id))
- extruder_definition_metadata = metadata_list[0]
- extruder_position = str(extruder_definition_metadata["position"])
-
+ extruder_position = node.metadata.get("position")
+ if extruder_position is None: #Then we're a global quality changes profile.
+ if self.node_for_global is not None:
+ raise RuntimeError("{group} tries to overwrite the existing node_for_global {original_global} with {new_global}".format(group = self, original_global = self.node_for_global, new_global = node))
+ self.node_for_global = node
+ else: #This is an extruder's quality changes profile.
if extruder_position in self.nodes_for_extruders:
raise RuntimeError("%s tries to overwrite the existing nodes_for_extruders position [%s] %s with %s" %
(self, extruder_position, self.node_for_global, node))
-
self.nodes_for_extruders[extruder_position] = node
- else:
- # This is a quality_changes for the GlobalStack
- if self.node_for_global is not None:
- raise RuntimeError("%s tries to overwrite the existing node_for_global %s with %s" %
- (self, self.node_for_global, node))
- self.node_for_global = node
-
def __str__(self) -> str:
return "%s[<%s>, available = %s]" % (self.__class__.__name__, self.name, self.is_available)
diff --git a/cura/Machines/QualityGroup.py b/cura/Machines/QualityGroup.py
index 6945162401..02096cfb36 100644
--- a/cura/Machines/QualityGroup.py
+++ b/cura/Machines/QualityGroup.py
@@ -1,7 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from typing import Optional, List
+from typing import Dict, Optional, List
from PyQt5.QtCore import QObject, pyqtSlot
@@ -25,7 +25,7 @@ class QualityGroup(QObject):
super().__init__(parent)
self.name = name
self.node_for_global = None # type: Optional["QualityGroup"]
- self.nodes_for_extruders = dict() # position str -> QualityGroup
+ self.nodes_for_extruders = {} # type: Dict[int, "QualityGroup"]
self.quality_type = quality_type
self.is_available = False
diff --git a/cura/Machines/QualityManager.py b/cura/Machines/QualityManager.py
index a2871880f0..8d972c9192 100644
--- a/cura/Machines/QualityManager.py
+++ b/cura/Machines/QualityManager.py
@@ -16,6 +16,7 @@ from .QualityGroup import QualityGroup
from .QualityNode import QualityNode
if TYPE_CHECKING:
+ from UM.Settings.DefinitionContainer import DefinitionContainer
from cura.Settings.GlobalStack import GlobalStack
from .QualityChangesGroup import QualityChangesGroup
@@ -159,9 +160,9 @@ class QualityManager(QObject):
# Updates the given quality groups' availabilities according to which extruders are being used/ enabled.
def _updateQualityGroupsAvailability(self, machine: "GlobalStack", quality_group_list):
used_extruders = set()
- # TODO: This will change after the Machine refactoring
for i in range(machine.getProperty("machine_extruder_count", "value")):
- used_extruders.add(str(i))
+ if machine.extruders[str(i)].isEnabled:
+ used_extruders.add(str(i))
# Update the "is_available" flag for each quality group.
for quality_group in quality_group_list:
@@ -178,7 +179,7 @@ class QualityManager(QObject):
# Returns a dict of "custom profile name" -> QualityChangesGroup
def getQualityChangesGroups(self, machine: "GlobalStack") -> dict:
- machine_definition_id = getMachineDefinitionIDForQualitySearch(machine)
+ machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
machine_node = self._machine_quality_type_to_quality_changes_dict.get(machine_definition_id)
if not machine_node:
@@ -206,7 +207,7 @@ class QualityManager(QObject):
# For more details, see QualityGroup.
#
def getQualityGroups(self, machine: "GlobalStack") -> dict:
- machine_definition_id = getMachineDefinitionIDForQualitySearch(machine)
+ machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
# This determines if we should only get the global qualities for the global stack and skip the global qualities for the extruder stacks
has_variant_materials = parseBool(machine.getMetaDataEntry("has_variant_materials", False))
@@ -315,7 +316,7 @@ class QualityManager(QObject):
return quality_group_dict
def getQualityGroupsForMachineDefinition(self, machine: "GlobalStack") -> dict:
- machine_definition_id = getMachineDefinitionIDForQualitySearch(machine)
+ machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
# To find the quality container for the GlobalStack, check in the following fall-back manner:
# (1) the machine-specific node
@@ -386,7 +387,7 @@ class QualityManager(QObject):
if quality_changes_group is None:
# create global quality changes only
new_quality_changes = self._createQualityChanges(quality_group.quality_type, quality_changes_name,
- global_stack, extruder_id = None)
+ global_stack, None)
self._container_registry.addContainer(new_quality_changes)
else:
new_name = self._container_registry.uniqueName(quality_changes_name)
@@ -428,11 +429,11 @@ class QualityManager(QObject):
Logger.log("w", "No quality or quality changes container found in stack %s, ignoring it", stack.getId())
continue
- extruder_definition_id = None
- if isinstance(stack, ExtruderStack):
- extruder_definition_id = stack.definition.getId()
quality_type = quality_container.getMetaDataEntry("quality_type")
- new_changes = self._createQualityChanges(quality_type, unique_name, global_stack, extruder_definition_id)
+ extruder_stack = None
+ if isinstance(stack, ExtruderStack):
+ extruder_stack = stack
+ new_changes = self._createQualityChanges(quality_type, unique_name, global_stack, extruder_stack)
from cura.Settings.ContainerManager import ContainerManager
ContainerManager.getInstance()._performMerge(new_changes, quality_changes_container, clear_settings = False)
ContainerManager.getInstance()._performMerge(new_changes, user_container)
@@ -443,8 +444,8 @@ class QualityManager(QObject):
# Create a quality changes container with the given setup.
#
def _createQualityChanges(self, quality_type: str, new_name: str, machine: "GlobalStack",
- extruder_id: Optional[str]) -> "InstanceContainer":
- base_id = machine.definition.getId() if extruder_id is None else extruder_id
+ extruder_stack: Optional["ExtruderStack"]) -> "InstanceContainer":
+ base_id = machine.definition.getId() if extruder_stack is None else extruder_stack.getId()
new_id = base_id + "_" + new_name
new_id = new_id.lower().replace(" ", "_")
new_id = self._container_registry.uniqueName(new_id)
@@ -456,11 +457,11 @@ class QualityManager(QObject):
quality_changes.addMetaDataEntry("quality_type", quality_type)
# If we are creating a container for an extruder, ensure we add that to the container
- if extruder_id is not None:
- quality_changes.addMetaDataEntry("extruder", extruder_id)
+ if extruder_stack is not None:
+ quality_changes.addMetaDataEntry("position", extruder_stack.getMetaDataEntry("position"))
# If the machine specifies qualities should be filtered, ensure we match the current criteria.
- machine_definition_id = getMachineDefinitionIDForQualitySearch(machine)
+ machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
quality_changes.setDefinition(machine_definition_id)
quality_changes.addMetaDataEntry("setting_version", self._application.SettingVersion)
@@ -480,12 +481,13 @@ class QualityManager(QObject):
# Example: for an Ultimaker 3 Extended, it has "quality_definition = ultimaker3". This means Ultimaker 3 Extended
# shares the same set of qualities profiles as Ultimaker 3.
#
-def getMachineDefinitionIDForQualitySearch(machine: "GlobalStack", default_definition_id: str = "fdmprinter") -> str:
+def getMachineDefinitionIDForQualitySearch(machine_definition: "DefinitionContainer",
+ default_definition_id: str = "fdmprinter") -> str:
machine_definition_id = default_definition_id
- if parseBool(machine.getMetaDataEntry("has_machine_quality", False)):
+ if parseBool(machine_definition.getMetaDataEntry("has_machine_quality", False)):
# Only use the machine's own quality definition ID if this machine has machine quality.
- machine_definition_id = machine.getMetaDataEntry("quality_definition")
+ machine_definition_id = machine_definition.getMetaDataEntry("quality_definition")
if machine_definition_id is None:
- machine_definition_id = machine.definition.getId()
+ machine_definition_id = machine_definition.getId()
return machine_definition_id
diff --git a/cura/Machines/VariantManager.py b/cura/Machines/VariantManager.py
index 6cb0a3d7b2..4e033e054e 100644
--- a/cura/Machines/VariantManager.py
+++ b/cura/Machines/VariantManager.py
@@ -25,7 +25,7 @@ ALL_VARIANT_TYPES = (VariantType.BUILD_PLATE, VariantType.NOZZLE)
#
-# VariantManager is THE place to look for a specific variant. It maintains a variant lookup table with the following
+# VariantManager is THE place to look for a specific variant. It maintains two variant lookup tables with the following
# structure:
#
# [machine_definition_id] -> [variant_type] -> [variant_name] -> ContainerNode(metadata / container)
@@ -35,6 +35,9 @@ ALL_VARIANT_TYPES = (VariantType.BUILD_PLATE, VariantType.NOZZLE)
# -> "BB 0.8"
# -> ...
#
+# [machine_definition_id] -> [machine_buildplate_type] -> ContainerNode(metadata / container)
+# Example: "ultimaker3" -> "glass" (this is different from the variant name) -> ContainerNode
+#
# Note that the "container" field is not loaded in the beginning because it would defeat the purpose of lazy-loading.
# A container is loaded when getVariant() is called to load a variant InstanceContainer.
#
@@ -44,6 +47,7 @@ class VariantManager:
self._container_registry = container_registry # type: ContainerRegistry
self._machine_to_variant_dict_map = dict() # ->
+ self._machine_to_buildplate_dict_map = dict()
self._exclude_variant_id_list = ["empty_variant"]
@@ -53,6 +57,7 @@ class VariantManager:
#
def initialize(self):
self._machine_to_variant_dict_map = OrderedDict()
+ self._machine_to_buildplate_dict_map = OrderedDict()
# Cache all variants from the container registry to a variant map for better searching and organization.
variant_metadata_list = self._container_registry.findContainersMetadata(type = "variant")
@@ -78,16 +83,40 @@ class VariantManager:
variant_dict[variant_name] = ContainerNode(metadata = variant_metadata)
+ # If the variant is a buildplate then fill also the buildplate map
+ if variant_type == VariantType.BUILD_PLATE:
+ if variant_definition not in self._machine_to_buildplate_dict_map:
+ self._machine_to_buildplate_dict_map[variant_definition] = OrderedDict()
+
+ variant_container = self._container_registry.findContainers(type = "variant", id = variant_metadata["id"])
+ if not variant_container:
+ # ERROR: not variant container. This should never happen
+ raise RuntimeError("Not variant found [%s], type [%s] for machine [%s]" %
+ (variant_name, variant_type, variant_definition))
+ buildplate_type = variant_container[0].getProperty("machine_buildplate_type", "value")
+ if buildplate_type not in self._machine_to_buildplate_dict_map[variant_definition]:
+ self._machine_to_variant_dict_map[variant_definition][buildplate_type] = dict()
+
+ self._machine_to_buildplate_dict_map[variant_definition][buildplate_type] = variant_dict[variant_name]
+
#
# Gets the variant InstanceContainer with the given information.
# Almost the same as getVariantMetadata() except that this returns an InstanceContainer if present.
#
def getVariantNode(self, machine_definition_id: str, variant_name: str,
- variant_type: Optional["VariantType"] = VariantType.NOZZLE) -> Optional["ContainerNode"]:
+ variant_type: Optional["VariantType"] = None) -> Optional["ContainerNode"]:
+ if variant_type is None:
+ variant_node = None
+ variant_type_dict = self._machine_to_variant_dict_map[machine_definition_id]
+ for variant_dict in variant_type_dict.values():
+ if variant_name in variant_dict:
+ variant_node = variant_dict[variant_name]
+ break
+ return variant_node
return self._machine_to_variant_dict_map[machine_definition_id].get(variant_type, {}).get(variant_name)
def getVariantNodes(self, machine: "GlobalStack",
- variant_type: Optional["VariantType"] = VariantType.NOZZLE) -> dict:
+ variant_type: Optional["VariantType"] = None) -> dict:
machine_definition_id = machine.definition.getId()
return self._machine_to_variant_dict_map.get(machine_definition_id, {}).get(variant_type, {})
@@ -109,3 +138,8 @@ class VariantManager:
if preferred_variant_name:
node = self.getVariantNode(machine_definition_id, preferred_variant_name, variant_type)
return node
+
+ def getBuildplateVariantNode(self, machine_definition_id: str, buildplate_type: str) -> Optional["ContainerNode"]:
+ if machine_definition_id in self._machine_to_buildplate_dict_map:
+ return self._machine_to_buildplate_dict_map[machine_definition_id].get(buildplate_type)
+ return None
diff --git a/cura/MultiplyObjectsJob.py b/cura/MultiplyObjectsJob.py
index 441d4c96c3..b9f37ec6f8 100644
--- a/cura/MultiplyObjectsJob.py
+++ b/cura/MultiplyObjectsJob.py
@@ -33,6 +33,7 @@ class MultiplyObjectsJob(Job):
root = scene.getRoot()
arranger = Arrange.create(scene_root=root)
nodes = []
+
for node in self._objects:
# If object is part of a group, multiply group
current_node = node
@@ -49,18 +50,20 @@ class MultiplyObjectsJob(Job):
for i in range(self._count):
# We do place the nodes one by one, as we want to yield in between.
if not node_too_big:
- node, solution_found = arranger.findNodePlacement(current_node, offset_shape_arr, hull_shape_arr)
+ new_node, solution_found = arranger.findNodePlacement(current_node, offset_shape_arr, hull_shape_arr)
if node_too_big or not solution_found:
found_solution_for_all = False
- new_location = node.getPosition()
+ new_location = new_node.getPosition()
new_location = new_location.set(z = 100 - i * 20)
- node.setPosition(new_location)
+ new_node.setPosition(new_location)
# Same build plate
build_plate_number = current_node.callDecoration("getBuildPlateNumber")
- node.callDecoration("setBuildPlateNumber", build_plate_number)
+ new_node.callDecoration("setBuildPlateNumber", build_plate_number)
+ for child in new_node.getChildren():
+ child.callDecoration("setBuildPlateNumber", build_plate_number)
- nodes.append(node)
+ nodes.append(new_node)
current_progress += 1
status_message.setProgress((current_progress / total_progress) * 100)
Job.yieldThread()
diff --git a/cura/ObjectsModel.py b/cura/ObjectsModel.py
index d7077d3d85..cfe4320e28 100644
--- a/cura/ObjectsModel.py
+++ b/cura/ObjectsModel.py
@@ -1,3 +1,8 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from PyQt5.QtCore import QTimer
+
from UM.Application import Application
from UM.Qt.ListModel import ListModel
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
@@ -14,17 +19,23 @@ class ObjectsModel(ListModel):
def __init__(self):
super().__init__()
- Application.getInstance().getController().getScene().sceneChanged.connect(self._update)
- Preferences.getInstance().preferenceChanged.connect(self._update)
+ Application.getInstance().getController().getScene().sceneChanged.connect(self._updateDelayed)
+ Preferences.getInstance().preferenceChanged.connect(self._updateDelayed)
+
+ self._update_timer = QTimer()
+ self._update_timer.setInterval(100)
+ self._update_timer.setSingleShot(True)
+ self._update_timer.timeout.connect(self._update)
self._build_plate_number = -1
- self._stacks_have_errors = None # type:Optional[bool]
-
def setActiveBuildPlate(self, nr):
self._build_plate_number = nr
self._update()
+ def _updateDelayed(self, *args):
+ self._update_timer.start()
+
def _update(self, *args):
nodes = []
filter_current_build_plate = Preferences.getInstance().getValue("view/filter_current_build_plate")
@@ -69,11 +80,3 @@ class ObjectsModel(ListModel):
@staticmethod
def createObjectsModel():
return ObjectsModel()
-
- ## Check if none of the model's stacks contain error states
- # The setting applied for the settings per model
- def stacksHaveErrors(self) -> bool:
- return bool(self._stacks_have_errors)
-
- def setStacksHaveErrors(self, value):
- self._stacks_have_errors = value
\ No newline at end of file
diff --git a/cura/PickingPass.py b/cura/PickingPass.py
new file mode 100644
index 0000000000..2a1abe8f63
--- /dev/null
+++ b/cura/PickingPass.py
@@ -0,0 +1,69 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+from UM.Application import Application
+from UM.Math.Vector import Vector
+from UM.Resources import Resources
+
+from UM.View.RenderPass import RenderPass
+from UM.View.GL.OpenGL import OpenGL
+from UM.View.RenderBatch import RenderBatch
+
+from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
+
+
+## A RenderPass subclass that renders a the distance of selectable objects from the active camera to a texture.
+# The texture is used to map a 2d location (eg the mouse location) to a world space position
+#
+# Note that in order to increase precision, the 24 bit depth value is encoded into all three of the R,G & B channels
+class PickingPass(RenderPass):
+ def __init__(self, width: int, height: int):
+ super().__init__("picking", width, height)
+
+ self._renderer = Application.getInstance().getRenderer()
+
+ self._shader = None
+ self._scene = Application.getInstance().getController().getScene()
+
+ def render(self) -> None:
+ if not self._shader:
+ self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "camera_distance.shader"))
+
+ width, height = self.getSize()
+ self._gl.glViewport(0, 0, width, height)
+ self._gl.glClearColor(1.0, 1.0, 1.0, 0.0)
+ self._gl.glClear(self._gl.GL_COLOR_BUFFER_BIT | self._gl.GL_DEPTH_BUFFER_BIT)
+
+ # Create a new batch to be rendered
+ batch = RenderBatch(self._shader)
+
+ # Fill up the batch with objects that can be sliced. `
+ for node in DepthFirstIterator(self._scene.getRoot()):
+ if node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible():
+ batch.addItem(node.getWorldTransformation(), node.getMeshData())
+
+ self.bind()
+ batch.render(self._scene.getActiveCamera())
+ self.release()
+
+ ## Get the distance in mm from the camera to at a certain pixel coordinate.
+ def getPickedDepth(self, x: int, y: int) -> float:
+ output = self.getOutput()
+
+ window_size = self._renderer.getWindowSize()
+
+ px = (0.5 + x / 2.0) * window_size[0]
+ py = (0.5 + y / 2.0) * window_size[1]
+
+ if px < 0 or px > (output.width() - 1) or py < 0 or py > (output.height() - 1):
+ return -1
+
+ distance = output.pixel(px, py) # distance in micron, from in r, g & b channels
+ distance = (distance & 0x00ffffff) / 1000. # drop the alpha channel and covert to mm
+ return distance
+
+ ## Get the world coordinates of a picked point
+ def getPickedPosition(self, x: int, y: int) -> Vector:
+ distance = self.getPickedDepth(x, y)
+ ray = self._scene.getActiveCamera().getRay(x, y)
+
+ return ray.getPointAlongRay(distance)
diff --git a/cura/PlatformPhysics.py b/cura/PlatformPhysics.py
index 69890178e4..1a5d6ef837 100755
--- a/cura/PlatformPhysics.py
+++ b/cura/PlatformPhysics.py
@@ -40,6 +40,8 @@ class PlatformPhysics:
Preferences.getInstance().addPreference("physics/automatic_drop_down", True)
def _onSceneChanged(self, source):
+ if not source.getMeshData():
+ return
self._change_timer.start()
def _onChangeTimerFinished(self):
@@ -69,7 +71,7 @@ class PlatformPhysics:
# Move it downwards if bottom is above platform
move_vector = Vector()
- if Preferences.getInstance().getValue("physics/automatic_drop_down") and not (node.getParent() and node.getParent().callDecoration("isGroup")) and node.isEnabled(): #If an object is grouped, don't move it down
+ if Preferences.getInstance().getValue("physics/automatic_drop_down") and not (node.getParent() and node.getParent().callDecoration("isGroup") or node.getParent() != root) and node.isEnabled(): #If an object is grouped, don't move it down
z_offset = node.callDecoration("getZOffset") if node.getDecorator(ZOffsetDecorator.ZOffsetDecorator) else 0
move_vector = move_vector.set(y = -bbox.bottom + z_offset)
diff --git a/cura/PrintInformation.py b/cura/PrintInformation.py
index 05b740637d..6d73489448 100644
--- a/cura/PrintInformation.py
+++ b/cura/PrintInformation.py
@@ -309,16 +309,13 @@ class PrintInformation(QObject):
self.jobNameChanged.emit()
- @pyqtProperty(str)
- def baseName(self):
- return self._base_name
-
@pyqtSlot(str)
def setProjectName(self, name):
self.setBaseName(name, is_project_file = True)
- @pyqtSlot(str)
- def setBaseName(self, base_name, is_project_file = False):
+ baseNameChanged = pyqtSignal()
+
+ def setBaseName(self, base_name: str, is_project_file: bool = False):
# Ensure that we don't use entire path but only filename
name = os.path.basename(base_name)
@@ -336,6 +333,9 @@ class PrintInformation(QObject):
self._base_name = name
self._updateJobName()
+ @pyqtProperty(str, fset = setBaseName, notify = baseNameChanged)
+ def baseName(self):
+ return self._base_name
## Created an acronymn-like abbreviated machine name from the currently active machine name
# Called each time the global stack is switched
@@ -395,7 +395,6 @@ class PrintInformation(QObject):
## Listen to scene changes to check if we need to reset the print information
def _onSceneChanged(self, scene_node):
-
# Ignore any changes that are not related to sliceable objects
if not isinstance(scene_node, SceneNode)\
or not scene_node.callDecoration("isSliceable")\
diff --git a/cura/PrinterOutput/ConfigurationModel.py b/cura/PrinterOutput/ConfigurationModel.py
new file mode 100644
index 0000000000..c03d968b9e
--- /dev/null
+++ b/cura/PrinterOutput/ConfigurationModel.py
@@ -0,0 +1,81 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal
+from typing import List
+
+MYPY = False
+if MYPY:
+ from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel
+
+
+class ConfigurationModel(QObject):
+
+ configurationChanged = pyqtSignal()
+
+ def __init__(self):
+ super().__init__()
+ self._printer_type = None
+ self._extruder_configurations = [] # type: List[ExtruderConfigurationModel]
+ self._buildplate_configuration = None
+
+ def setPrinterType(self, printer_type):
+ self._printer_type = printer_type
+
+ @pyqtProperty(str, fset = setPrinterType, notify = configurationChanged)
+ def printerType(self):
+ return self._printer_type
+
+ def setExtruderConfigurations(self, extruder_configurations):
+ self._extruder_configurations = extruder_configurations
+
+ @pyqtProperty("QVariantList", fset = setExtruderConfigurations, notify = configurationChanged)
+ def extruderConfigurations(self):
+ return self._extruder_configurations
+
+ def setBuildplateConfiguration(self, buildplate_configuration):
+ self._buildplate_configuration = buildplate_configuration
+
+ @pyqtProperty(str, fset = setBuildplateConfiguration, notify = configurationChanged)
+ def buildplateConfiguration(self):
+ return self._buildplate_configuration
+
+ ## This method is intended to indicate whether the configuration is valid or not.
+ # The method checks if the mandatory fields are or not set
+ def isValid(self):
+ if not self._extruder_configurations:
+ return False
+ for configuration in self._extruder_configurations:
+ if configuration is None:
+ return False
+ return self._printer_type is not None
+
+ def __str__(self):
+ message_chunks = []
+ message_chunks.append("Printer type: " + self._printer_type)
+ message_chunks.append("Extruders: [")
+ for configuration in self._extruder_configurations:
+ message_chunks.append(" " + str(configuration))
+ message_chunks.append("]")
+ if self._buildplate_configuration is not None:
+ message_chunks.append("Buildplate: " + self._buildplate_configuration)
+
+ return "\n".join(message_chunks)
+
+ def __eq__(self, other):
+ return hash(self) == hash(other)
+
+ ## The hash function is used to compare and create unique sets. The configuration is unique if the configuration
+ # of the extruders is unique (the order of the extruders matters), and the type and buildplate is the same.
+ def __hash__(self):
+ extruder_hash = hash(0)
+ first_extruder = None
+ for configuration in self._extruder_configurations:
+ extruder_hash ^= hash(configuration)
+ if configuration.position == 0:
+ first_extruder = configuration
+ # To ensure the correct order of the extruders, we add an "and" operation using the first extruder hash value
+ if first_extruder:
+ extruder_hash &= hash(first_extruder)
+
+ return hash(self._printer_type) ^ extruder_hash ^ hash(self._buildplate_configuration)
\ No newline at end of file
diff --git a/cura/PrinterOutput/ExtruderConfigurationModel.py b/cura/PrinterOutput/ExtruderConfigurationModel.py
new file mode 100644
index 0000000000..bc7f1a7c07
--- /dev/null
+++ b/cura/PrinterOutput/ExtruderConfigurationModel.py
@@ -0,0 +1,59 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal
+
+
+class ExtruderConfigurationModel(QObject):
+
+ extruderConfigurationChanged = pyqtSignal()
+
+ def __init__(self):
+ super().__init__()
+ self._position = -1
+ self._material = None
+ self._hotend_id = None
+
+ def setPosition(self, position):
+ self._position = position
+
+ @pyqtProperty(int, fset = setPosition, notify = extruderConfigurationChanged)
+ def position(self):
+ return self._position
+
+ def setMaterial(self, material):
+ self._material = material
+
+ @pyqtProperty(QObject, fset = setMaterial, notify = extruderConfigurationChanged)
+ def material(self):
+ return self._material
+
+ def setHotendID(self, hotend_id):
+ self._hotend_id = hotend_id
+
+ @pyqtProperty(str, fset = setHotendID, notify = extruderConfigurationChanged)
+ def hotendID(self):
+ return self._hotend_id
+
+ ## This method is intended to indicate whether the configuration is valid or not.
+ # The method checks if the mandatory fields are or not set
+ # At this moment is always valid since we allow to have empty material and variants.
+ def isValid(self):
+ return True
+
+ def __str__(self):
+ message_chunks = []
+ message_chunks.append("Position: " + str(self._position))
+ message_chunks.append("-")
+ message_chunks.append("Material: " + self.material.type if self.material else "empty")
+ message_chunks.append("-")
+ message_chunks.append("HotendID: " + self.hotendID if self.hotendID else "empty")
+ return " ".join(message_chunks)
+
+ def __eq__(self, other):
+ return hash(self) == hash(other)
+
+ # Calculating a hash function using the position of the extruder, the material GUID and the hotend id to check if is
+ # unique within a set
+ def __hash__(self):
+ return hash(self._position) ^ (hash(self._material.guid) if self._material is not None else hash(0)) ^ hash(self._hotend_id)
\ No newline at end of file
diff --git a/cura/PrinterOutput/ExtruderOuputModel.py b/cura/PrinterOutput/ExtruderOutputModel.py
similarity index 52%
rename from cura/PrinterOutput/ExtruderOuputModel.py
rename to cura/PrinterOutput/ExtruderOutputModel.py
index b0be6cbbe4..75b9cc98ac 100644
--- a/cura/PrinterOutput/ExtruderOuputModel.py
+++ b/cura/PrinterOutput/ExtruderOutputModel.py
@@ -1,8 +1,8 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant, pyqtSlot
-from UM.Logger import Logger
+from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot
+from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel
from typing import Optional
@@ -17,14 +17,34 @@ class ExtruderOutputModel(QObject):
targetHotendTemperatureChanged = pyqtSignal()
hotendTemperatureChanged = pyqtSignal()
activeMaterialChanged = pyqtSignal()
+ extruderConfigurationChanged = pyqtSignal()
+ isPreheatingChanged = pyqtSignal()
- def __init__(self, printer: "PrinterOutputModel", parent=None):
+ def __init__(self, printer: "PrinterOutputModel", position, parent=None):
super().__init__(parent)
self._printer = printer
+ self._position = position
self._target_hotend_temperature = 0
self._hotend_temperature = 0
self._hotend_id = ""
self._active_material = None # type: Optional[MaterialOutputModel]
+ self._extruder_configuration = ExtruderConfigurationModel()
+ self._extruder_configuration.position = self._position
+
+ self._is_preheating = False
+
+ def getPrinter(self):
+ return self._printer
+
+ def getPosition(self):
+ return self._position
+
+ # Does the printer support pre-heating the bed at all
+ @pyqtProperty(bool, constant=True)
+ def canPreHeatHotends(self):
+ if self._printer:
+ return self._printer.canPreHeatHotends
+ return False
@pyqtProperty(QObject, notify = activeMaterialChanged)
def activeMaterial(self) -> "MaterialOutputModel":
@@ -33,7 +53,9 @@ class ExtruderOutputModel(QObject):
def updateActiveMaterial(self, material: Optional["MaterialOutputModel"]):
if self._active_material != material:
self._active_material = material
+ self._extruder_configuration.material = self._active_material
self.activeMaterialChanged.emit()
+ self.extruderConfigurationChanged.emit()
## Update the hotend temperature. This only changes it locally.
def updateHotendTemperature(self, temperature: float):
@@ -56,7 +78,7 @@ class ExtruderOutputModel(QObject):
def targetHotendTemperature(self) -> float:
return self._target_hotend_temperature
- @pyqtProperty(float, notify=hotendTemperatureChanged)
+ @pyqtProperty(float, notify = hotendTemperatureChanged)
def hotendTemperature(self) -> float:
return self._hotend_temperature
@@ -67,4 +89,34 @@ class ExtruderOutputModel(QObject):
def updateHotendID(self, id: str):
if self._hotend_id != id:
self._hotend_id = id
+ self._extruder_configuration.hotendID = self._hotend_id
self.hotendIDChanged.emit()
+ self.extruderConfigurationChanged.emit()
+
+ @pyqtProperty(QObject, notify = extruderConfigurationChanged)
+ def extruderConfiguration(self):
+ if self._extruder_configuration.isValid():
+ return self._extruder_configuration
+ return None
+
+ def updateIsPreheating(self, pre_heating):
+ if self._is_preheating != pre_heating:
+ self._is_preheating = pre_heating
+ self.isPreheatingChanged.emit()
+
+ @pyqtProperty(bool, notify=isPreheatingChanged)
+ def isPreheating(self):
+ return self._is_preheating
+
+ ## Pre-heats the extruder before printer.
+ #
+ # \param temperature The temperature to heat the extruder to, in degrees
+ # Celsius.
+ # \param duration How long the bed should stay warm, in seconds.
+ @pyqtSlot(float, float)
+ def preheatHotend(self, temperature, duration):
+ self._printer._controller.preheatHotend(self, temperature, duration)
+
+ @pyqtSlot()
+ def cancelPreheatHotend(self):
+ self._printer._controller.cancelPreheatHotend(self)
\ No newline at end of file
diff --git a/cura/PrinterOutput/GenericOutputController.py b/cura/PrinterOutput/GenericOutputController.py
new file mode 100644
index 0000000000..470848c208
--- /dev/null
+++ b/cura/PrinterOutput/GenericOutputController.py
@@ -0,0 +1,154 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
+from PyQt5.QtCore import QTimer
+
+MYPY = False
+if MYPY:
+ from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
+ from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
+
+
+class GenericOutputController(PrinterOutputController):
+ def __init__(self, output_device):
+ super().__init__(output_device)
+
+ self._preheat_bed_timer = QTimer()
+ self._preheat_bed_timer.setSingleShot(True)
+ self._preheat_bed_timer.timeout.connect(self._onPreheatBedTimerFinished)
+ self._preheat_printer = None
+
+ self._preheat_hotends_timer = QTimer()
+ self._preheat_hotends_timer.setSingleShot(True)
+ self._preheat_hotends_timer.timeout.connect(self._onPreheatHotendsTimerFinished)
+ self._preheat_hotends = set()
+
+ self._output_device.printersChanged.connect(self._onPrintersChanged)
+ self._active_printer = None
+
+ def _onPrintersChanged(self):
+ if self._active_printer:
+ self._active_printer.stateChanged.disconnect(self._onPrinterStateChanged)
+ self._active_printer.targetBedTemperatureChanged.disconnect(self._onTargetBedTemperatureChanged)
+ for extruder in self._active_printer.extruders:
+ extruder.targetHotendTemperatureChanged.disconnect(self._onTargetHotendTemperatureChanged)
+
+ self._active_printer = self._output_device.activePrinter
+ if self._active_printer:
+ self._active_printer.stateChanged.connect(self._onPrinterStateChanged)
+ self._active_printer.targetBedTemperatureChanged.connect(self._onTargetBedTemperatureChanged)
+ for extruder in self._active_printer.extruders:
+ extruder.targetHotendTemperatureChanged.connect(self._onTargetHotendTemperatureChanged)
+
+ def _onPrinterStateChanged(self):
+ if self._active_printer.state != "idle":
+ if self._preheat_bed_timer.isActive():
+ self._preheat_bed_timer.stop()
+ self._preheat_printer.updateIsPreheating(False)
+ if self._preheat_hotends_timer.isActive():
+ self._preheat_hotends_timer.stop()
+ for extruder in self._preheat_hotends:
+ extruder.updateIsPreheating(False)
+ self._preheat_hotends = set()
+
+ def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed):
+ self._output_device.sendCommand("G91")
+ self._output_device.sendCommand("G0 X%s Y%s Z%s F%s" % (x, y, z, speed))
+ self._output_device.sendCommand("G90")
+
+ def homeHead(self, printer):
+ self._output_device.sendCommand("G28 X")
+ self._output_device.sendCommand("G28 Y")
+
+ def homeBed(self, printer):
+ self._output_device.sendCommand("G28 Z")
+
+ def sendRawCommand(self, printer: "PrinterOutputModel", command: str):
+ self._output_device.sendCommand(command)
+
+ def setJobState(self, job: "PrintJobOutputModel", state: str):
+ if state == "pause":
+ self._output_device.pausePrint()
+ job.updateState("paused")
+ elif state == "print":
+ self._output_device.resumePrint()
+ job.updateState("printing")
+ elif state == "abort":
+ self._output_device.cancelPrint()
+ pass
+
+ def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int):
+ self._output_device.sendCommand("M140 S%s" % temperature)
+
+ def _onTargetBedTemperatureChanged(self):
+ if self._preheat_bed_timer.isActive() and self._preheat_printer.targetBedTemperature == 0:
+ self._preheat_bed_timer.stop()
+ self._preheat_printer.updateIsPreheating(False)
+
+ def preheatBed(self, printer: "PrinterOutputModel", temperature, duration):
+ try:
+ temperature = round(temperature) # The API doesn't allow floating point.
+ duration = round(duration)
+ except ValueError:
+ return # Got invalid values, can't pre-heat.
+
+ self.setTargetBedTemperature(printer, temperature=temperature)
+ self._preheat_bed_timer.setInterval(duration * 1000)
+ self._preheat_bed_timer.start()
+ self._preheat_printer = printer
+ printer.updateIsPreheating(True)
+
+ def cancelPreheatBed(self, printer: "PrinterOutputModel"):
+ self.setTargetBedTemperature(printer, temperature=0)
+ self._preheat_bed_timer.stop()
+ printer.updateIsPreheating(False)
+
+ def _onPreheatBedTimerFinished(self):
+ self.setTargetBedTemperature(self._preheat_printer, 0)
+ self._preheat_printer.updateIsPreheating(False)
+
+ def setTargetHotendTemperature(self, printer: "PrinterOutputModel", position: int, temperature: int):
+ self._output_device.sendCommand("M104 S%s T%s" % (temperature, position))
+
+ def _onTargetHotendTemperatureChanged(self):
+ if not self._preheat_hotends_timer.isActive():
+ return
+
+ for extruder in self._active_printer.extruders:
+ if extruder in self._preheat_hotends and extruder.targetHotendTemperature == 0:
+ extruder.updateIsPreheating(False)
+ self._preheat_hotends.remove(extruder)
+ if not self._preheat_hotends:
+ self._preheat_hotends_timer.stop()
+
+ def preheatHotend(self, extruder: "ExtruderOutputModel", temperature, duration):
+ position = extruder.getPosition()
+ number_of_extruders = len(extruder.getPrinter().extruders)
+ if position >= number_of_extruders:
+ return # Got invalid extruder nr, can't pre-heat.
+
+ try:
+ temperature = round(temperature) # The API doesn't allow floating point.
+ duration = round(duration)
+ except ValueError:
+ return # Got invalid values, can't pre-heat.
+
+ self.setTargetHotendTemperature(extruder.getPrinter(), position, temperature=temperature)
+ self._preheat_hotends_timer.setInterval(duration * 1000)
+ self._preheat_hotends_timer.start()
+ self._preheat_hotends.add(extruder)
+ extruder.updateIsPreheating(True)
+
+ def cancelPreheatHotend(self, extruder: "ExtruderOutputModel"):
+ self.setTargetHotendTemperature(extruder.getPrinter(), extruder.getPosition(), temperature=0)
+ if extruder in self._preheat_hotends:
+ extruder.updateIsPreheating(False)
+ self._preheat_hotends.remove(extruder)
+ if not self._preheat_hotends and self._preheat_hotends_timer.isActive():
+ self._preheat_hotends_timer.stop()
+
+ def _onPreheatHotendsTimerFinished(self):
+ for extruder in self._preheat_hotends:
+ self.setTargetHotendTemperature(extruder.getPrinter(), extruder.getPosition(), 0)
+ self._preheat_hotends = set()
diff --git a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py
index 315b195e2a..9da57a812e 100644
--- a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py
+++ b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py
@@ -1,8 +1,9 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Application import Application
from UM.Logger import Logger
+from cura.CuraApplication import CuraApplication
from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
@@ -55,6 +56,17 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
self._connection_state_before_timeout = None # type: Optional[ConnectionState]
+ printer_type = self._properties.get(b"machine", b"").decode("utf-8")
+ printer_type_identifiers = {
+ "9066": "ultimaker3",
+ "9511": "ultimaker3_extended"
+ }
+ self._printer_type = "Unknown"
+ for key, value in printer_type_identifiers.items():
+ if printer_type.startswith(key):
+ self._printer_type = value
+ break
+
def requestWrite(self, nodes, file_name=None, filter_by_machine=False, file_handler=None, **kwargs) -> None:
raise NotImplementedError("requestWrite needs to be implemented")
@@ -219,7 +231,6 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
reply.uploadProgress.connect(onProgress)
self._registerOnFinishedCallback(reply, onFinished)
-
return reply
def postForm(self, target: str, header_data: str, body_data: bytes, onFinished: Optional[Callable[[Any, QNetworkReply], None]], onProgress: Callable = None) -> None:
@@ -243,6 +254,9 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
self._last_manager_create_time = time()
self._manager.authenticationRequired.connect(self._onAuthenticationRequired)
+ machine_manager = CuraApplication.getInstance().getMachineManager()
+ machine_manager.checkCorrectGroupName(self.getId(), self.name)
+
def _registerOnFinishedCallback(self, reply: QNetworkReply, onFinished: Optional[Callable[[Any, QNetworkReply], None]]) -> None:
if onFinished is not None:
self._onFinishedCallbacks[reply.url().toString() + str(reply.operation())] = onFinished
@@ -301,6 +315,10 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
def firmwareVersion(self) -> str:
return self._properties.get(b"firmware_version", b"").decode("utf-8")
+ @pyqtProperty(str, constant=True)
+ def printerType(self) -> str:
+ return self._printer_type
+
## IPadress of this printer
@pyqtProperty(str, constant=True)
def ipAddress(self) -> str:
diff --git a/cura/PrinterOutput/PrinterOutputController.py b/cura/PrinterOutput/PrinterOutputController.py
index 86ca10e2d3..58c6ef05a7 100644
--- a/cura/PrinterOutput/PrinterOutputController.py
+++ b/cura/PrinterOutput/PrinterOutputController.py
@@ -6,7 +6,7 @@ from UM.Logger import Logger
MYPY = False
if MYPY:
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
- from cura.PrinterOutput.ExtruderOuputModel import ExtruderOuputModel
+ from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
@@ -15,10 +15,12 @@ class PrinterOutputController:
self.can_pause = True
self.can_abort = True
self.can_pre_heat_bed = True
+ self.can_pre_heat_hotends = True
+ self.can_send_raw_gcode = True
self.can_control_manually = True
self._output_device = output_device
- def setTargetHotendTemperature(self, printer: "PrinterOutputModel", extruder: "ExtruderOuputModel", temperature: int):
+ def setTargetHotendTemperature(self, printer: "PrinterOutputModel", extruder: "ExtruderOutputModel", temperature: int):
Logger.log("w", "Set target hotend temperature not implemented in controller")
def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int):
@@ -33,14 +35,23 @@ class PrinterOutputController:
def preheatBed(self, printer: "PrinterOutputModel", temperature, duration):
Logger.log("w", "Preheat bed not implemented in controller")
+ def cancelPreheatHotend(self, extruder: "ExtruderOutputModel"):
+ Logger.log("w", "Cancel preheat hotend not implemented in controller")
+
+ def preheatHotend(self, extruder: "ExtruderOutputModel", temperature, duration):
+ Logger.log("w", "Preheat hotend not implemented in controller")
+
def setHeadPosition(self, printer: "PrinterOutputModel", x, y, z, speed):
Logger.log("w", "Set head position not implemented in controller")
def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed):
Logger.log("w", "Move head not implemented in controller")
- def homeBed(self, printer):
+ def homeBed(self, printer: "PrinterOutputModel"):
Logger.log("w", "Home bed not implemented in controller")
- def homeHead(self, printer):
- Logger.log("w", "Home head not implemented in controller")
\ No newline at end of file
+ def homeHead(self, printer: "PrinterOutputModel"):
+ Logger.log("w", "Home head not implemented in controller")
+
+ def sendRawCommand(self, printer: "PrinterOutputModel", command: str):
+ Logger.log("w", "Custom command not implemented in controller")
diff --git a/cura/PrinterOutput/PrinterOutputModel.py b/cura/PrinterOutput/PrinterOutputModel.py
index 8234989519..928a882c8c 100644
--- a/cura/PrinterOutput/PrinterOutputModel.py
+++ b/cura/PrinterOutput/PrinterOutputModel.py
@@ -1,11 +1,11 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant, pyqtSlot
-from UM.Logger import Logger
-from typing import Optional, List
+from typing import Optional
from UM.Math.Vector import Vector
-from cura.PrinterOutput.ExtruderOuputModel import ExtruderOutputModel
+from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
+from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel
MYPY = False
if MYPY:
@@ -22,8 +22,10 @@ class PrinterOutputModel(QObject):
nameChanged = pyqtSignal()
headPositionChanged = pyqtSignal()
keyChanged = pyqtSignal()
- typeChanged = pyqtSignal()
+ printerTypeChanged = pyqtSignal()
+ buildplateChanged = pyqtSignal()
cameraChanged = pyqtSignal()
+ configurationChanged = pyqtSignal()
def __init__(self, output_controller: "PrinterOutputController", number_of_extruders: int = 1, parent=None, firmware_version = ""):
super().__init__(parent)
@@ -32,13 +34,18 @@ class PrinterOutputModel(QObject):
self._name = ""
self._key = "" # Unique identifier
self._controller = output_controller
- self._extruders = [ExtruderOutputModel(printer=self) for i in range(number_of_extruders)]
+ self._extruders = [ExtruderOutputModel(printer = self, position = i) for i in range(number_of_extruders)]
+ self._printer_configuration = ConfigurationModel() # Indicates the current configuration setup in this printer
self._head_position = Vector(0, 0, 0)
self._active_print_job = None # type: Optional[PrintJobOutputModel]
self._firmware_version = firmware_version
self._printer_state = "unknown"
self._is_preheating = False
- self._type = ""
+ self._printer_type = ""
+ self._buildplate_name = None
+ # Update the printer configuration every time any of the extruders changes its configuration
+ for extruder in self._extruders:
+ extruder.extruderConfigurationChanged.connect(self._updateExtruderConfiguration)
self._camera = None
@@ -64,14 +71,27 @@ class PrinterOutputModel(QObject):
def camera(self):
return self._camera
- @pyqtProperty(str, notify = typeChanged)
+ @pyqtProperty(str, notify = printerTypeChanged)
def type(self):
- return self._type
+ return self._printer_type
- def updateType(self, type):
- if self._type != type:
- self._type = type
- self.typeChanged.emit()
+ def updateType(self, printer_type):
+ if self._printer_type != printer_type:
+ self._printer_type = printer_type
+ self._printer_configuration.printerType = self._printer_type
+ self.printerTypeChanged.emit()
+ self.configurationChanged.emit()
+
+ @pyqtProperty(str, notify = buildplateChanged)
+ def buildplate(self):
+ return self._buildplate_name
+
+ def updateBuildplateName(self, buildplate_name):
+ if self._buildplate_name != buildplate_name:
+ self._buildplate_name = buildplate_name
+ self._printer_configuration.buildplateConfiguration = self._buildplate_name
+ self.buildplateChanged.emit()
+ self.configurationChanged.emit()
@pyqtProperty(str, notify=keyChanged)
def key(self):
@@ -90,6 +110,10 @@ class PrinterOutputModel(QObject):
def homeBed(self):
self._controller.homeBed(self)
+ @pyqtSlot(str)
+ def sendRawCommand(self, command: str):
+ self._controller.sendRawCommand(self, command)
+
@pyqtProperty("QVariantList", constant = True)
def extruders(self):
return self._extruders
@@ -103,32 +127,32 @@ class PrinterOutputModel(QObject):
self._head_position = Vector(x, y, z)
self.headPositionChanged.emit()
- @pyqtProperty("long", "long", "long")
- @pyqtProperty("long", "long", "long", "long")
+ @pyqtProperty(float, float, float)
+ @pyqtProperty(float, float, float, float)
def setHeadPosition(self, x, y, z, speed = 3000):
self.updateHeadPosition(x, y, z)
self._controller.setHeadPosition(self, x, y, z, speed)
- @pyqtProperty("long")
- @pyqtProperty("long", "long")
+ @pyqtProperty(float)
+ @pyqtProperty(float, float)
def setHeadX(self, x, speed = 3000):
self.updateHeadPosition(x, self._head_position.y, self._head_position.z)
self._controller.setHeadPosition(self, x, self._head_position.y, self._head_position.z, speed)
- @pyqtProperty("long")
- @pyqtProperty("long", "long")
+ @pyqtProperty(float)
+ @pyqtProperty(float, float)
def setHeadY(self, y, speed = 3000):
self.updateHeadPosition(self._head_position.x, y, self._head_position.z)
self._controller.setHeadPosition(self, self._head_position.x, y, self._head_position.z, speed)
- @pyqtProperty("long")
- @pyqtProperty("long", "long")
+ @pyqtProperty(float)
+ @pyqtProperty(float, float)
def setHeadZ(self, z, speed = 3000):
self.updateHeadPosition(self._head_position.x, self._head_position.y, z)
self._controller.setHeadPosition(self, self._head_position.x, self._head_position.y, z, speed)
- @pyqtSlot("long", "long", "long")
- @pyqtSlot("long", "long", "long", "long")
+ @pyqtSlot(float, float, float)
+ @pyqtSlot(float, float, float, float)
def moveHead(self, x = 0, y = 0, z = 0, speed = 3000):
self._controller.moveHead(self, x, y, z, speed)
@@ -218,6 +242,20 @@ class PrinterOutputModel(QObject):
return self._controller.can_pre_heat_bed
return False
+ # Does the printer support pre-heating the bed at all
+ @pyqtProperty(bool, constant=True)
+ def canPreHeatHotends(self):
+ if self._controller:
+ return self._controller.can_pre_heat_hotends
+ return False
+
+ # Does the printer support sending raw G-code at all
+ @pyqtProperty(bool, constant=True)
+ def canSendRawGcode(self):
+ if self._controller:
+ return self._controller.can_send_raw_gcode
+ return False
+
# Does the printer support pause at all
@pyqtProperty(bool, constant=True)
def canPause(self):
@@ -238,3 +276,14 @@ class PrinterOutputModel(QObject):
if self._controller:
return self._controller.can_control_manually
return False
+
+ # Returns the configuration (material, variant and buildplate) of the current printer
+ @pyqtProperty(QObject, notify = configurationChanged)
+ def printerConfiguration(self):
+ if self._printer_configuration.isValid():
+ return self._printer_configuration
+ return None
+
+ def _updateExtruderConfiguration(self):
+ self._printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in self._extruders]
+ self.configurationChanged.emit()
diff --git a/cura/PrinterOutputDevice.py b/cura/PrinterOutputDevice.py
index 9e603b83ae..4d6ddb8dfa 100644
--- a/cura/PrinterOutputDevice.py
+++ b/cura/PrinterOutputDevice.py
@@ -1,12 +1,11 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.i18n import i18nCatalog
from UM.OutputDevice.OutputDevice import OutputDevice
-from PyQt5.QtCore import pyqtProperty, QObject, QTimer, pyqtSignal
+from PyQt5.QtCore import pyqtProperty, QObject, QTimer, pyqtSignal, QVariant
from PyQt5.QtWidgets import QMessageBox
-
from UM.Logger import Logger
from UM.Signal import signalemitter
from UM.Application import Application
@@ -17,6 +16,7 @@ from typing import List, Optional
MYPY = False
if MYPY:
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
+ from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
i18n_catalog = i18nCatalog("cura")
@@ -44,10 +44,14 @@ class PrinterOutputDevice(QObject, OutputDevice):
# Signal to indicate that the info text about the connection has changed.
connectionTextChanged = pyqtSignal()
+ # Signal to indicate that the configuration of one of the printers has changed.
+ uniqueConfigurationsChanged = pyqtSignal()
+
def __init__(self, device_id, parent = None):
super().__init__(device_id = device_id, parent = parent)
self._printers = [] # type: List[PrinterOutputModel]
+ self._unique_configurations = [] # type: List[ConfigurationModel]
self._monitor_view_qml_path = ""
self._monitor_component = None
@@ -69,6 +73,8 @@ class PrinterOutputDevice(QObject, OutputDevice):
self._address = ""
self._connection_text = ""
+ self.printersChanged.connect(self._onPrintersChanged)
+ Application.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._updateUniqueConfigurations)
@pyqtProperty(str, notify = connectionTextChanged)
def address(self):
@@ -175,6 +181,23 @@ class PrinterOutputDevice(QObject, OutputDevice):
self.acceptsCommandsChanged.emit()
+ # Returns the unique configurations of the printers within this output device
+ @pyqtProperty("QVariantList", notify = uniqueConfigurationsChanged)
+ def uniqueConfigurations(self):
+ return self._unique_configurations
+
+ def _updateUniqueConfigurations(self):
+ self._unique_configurations = list(set([printer.printerConfiguration for printer in self._printers if printer.printerConfiguration is not None]))
+ self._unique_configurations.sort(key = lambda k: k.printerType)
+ self.uniqueConfigurationsChanged.emit()
+
+ def _onPrintersChanged(self):
+ for printer in self._printers:
+ printer.configurationChanged.connect(self._updateUniqueConfigurations)
+
+ # At this point there may be non-updated configurations
+ self._updateUniqueConfigurations()
+
## The current processing state of the backend.
class ConnectionState(IntEnum):
diff --git a/cura/ProfileReader.py b/cura/ProfileReader.py
index d4600ed99f..460fce823e 100644
--- a/cura/ProfileReader.py
+++ b/cura/ProfileReader.py
@@ -3,6 +3,13 @@
from UM.PluginObject import PluginObject
+
+# Exception when there is no profile to import from a given files.
+# Note that this should not be treated as an exception but as an information instead.
+class NoProfileException(Exception):
+ pass
+
+
## A type of plug-ins that reads profiles from a file.
#
# The profile is then stored as instance container of the type user profile.
@@ -14,4 +21,4 @@ class ProfileReader(PluginObject):
#
# \return \type{Profile|Profile[]} The profile that was obtained from the file or a list of Profiles.
def read(self, file_name):
- raise NotImplementedError("Profile reader plug-in was not correctly implemented. The read function was not implemented.")
\ No newline at end of file
+ raise NotImplementedError("Profile reader plug-in was not correctly implemented. The read function was not implemented.")
diff --git a/cura/Scene/BuildPlateDecorator.py b/cura/Scene/BuildPlateDecorator.py
index c2fd3145dd..dfb465b7ad 100644
--- a/cura/Scene/BuildPlateDecorator.py
+++ b/cura/Scene/BuildPlateDecorator.py
@@ -15,7 +15,7 @@ class BuildPlateDecorator(SceneNodeDecorator):
self._build_plate_number = nr
if isinstance(self._node, CuraSceneNode):
self._node.transformChanged() # trigger refresh node without introducing a new signal
- if self._node and self._node.callDecoration("isGroup"):
+ if self._node:
for child in self._node.getChildren():
child.callDecoration("setBuildPlateNumber", nr)
diff --git a/cura/Scene/ConvexHullDecorator.py b/cura/Scene/ConvexHullDecorator.py
index 3a563c2764..66bc8a7fc3 100644
--- a/cura/Scene/ConvexHullDecorator.py
+++ b/cura/Scene/ConvexHullDecorator.py
@@ -1,6 +1,8 @@
# Copyright (c) 2016 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
+from PyQt5.QtCore import QTimer
+
from UM.Application import Application
from UM.Math.Polygon import Polygon
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
@@ -22,6 +24,10 @@ class ConvexHullDecorator(SceneNodeDecorator):
self._global_stack = None
+ # Make sure the timer is created on the main thread
+ self._recompute_convex_hull_timer = None
+ Application.getInstance().callLater(self.createRecomputeConvexHullTimer)
+
self._raft_thickness = 0.0
# For raft thickness, DRY
self._build_volume = Application.getInstance().getBuildVolume()
@@ -33,6 +39,12 @@ class ConvexHullDecorator(SceneNodeDecorator):
self._onGlobalStackChanged()
+ def createRecomputeConvexHullTimer(self):
+ self._recompute_convex_hull_timer = QTimer()
+ self._recompute_convex_hull_timer.setInterval(200)
+ self._recompute_convex_hull_timer.setSingleShot(True)
+ self._recompute_convex_hull_timer.timeout.connect(self.recomputeConvexHull)
+
def setNode(self, node):
previous_node = self._node
# Disconnect from previous node signals
@@ -99,6 +111,12 @@ class ConvexHullDecorator(SceneNodeDecorator):
return self._compute2DConvexHull()
return None
+ def recomputeConvexHullDelayed(self):
+ if self._recompute_convex_hull_timer is not None:
+ self._recompute_convex_hull_timer.start()
+ else:
+ self.recomputeConvexHull()
+
def recomputeConvexHull(self):
controller = Application.getInstance().getController()
root = controller.getScene().getRoot()
@@ -279,7 +297,8 @@ class ConvexHullDecorator(SceneNodeDecorator):
def _onChanged(self, *args):
self._raft_thickness = self._build_volume.getRaftThickness()
- self.recomputeConvexHull()
+ if not args or args[0] == self._node:
+ self.recomputeConvexHullDelayed()
def _onGlobalStackChanged(self):
if self._global_stack:
diff --git a/cura/Scene/CuraSceneNode.py b/cura/Scene/CuraSceneNode.py
index df346baaad..48d271a2f2 100644
--- a/cura/Scene/CuraSceneNode.py
+++ b/cura/Scene/CuraSceneNode.py
@@ -1,9 +1,13 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+from copy import deepcopy
from typing import List
from UM.Application import Application
+from UM.Math.AxisAlignedBox import AxisAlignedBox
from UM.Scene.SceneNode import SceneNode
-from copy import deepcopy
-from cura.Settings.ExtrudersModel import ExtrudersModel
+
+from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
## Scene nodes that are models are only seen when selecting the corresponding build plate
@@ -11,6 +15,8 @@ from cura.Settings.ExtrudersModel import ExtrudersModel
class CuraSceneNode(SceneNode):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
+ if "no_setting_override" not in kwargs:
+ self.addDecorator(SettingOverrideDecorator()) # now we always have a getActiveExtruderPosition, unless explicitly disabled
self._outside_buildarea = False
def setOutsideBuildArea(self, new_value):
@@ -35,7 +41,7 @@ class CuraSceneNode(SceneNode):
# Use the support extruder instead of the active extruder if this is a support_mesh
if per_mesh_stack:
if per_mesh_stack.getProperty("support_mesh", "value"):
- return extruders[int(global_container_stack.getProperty("support_extruder_nr", "value"))]
+ return extruders[int(global_container_stack.getExtruderPositionValueWithDefault("support_extruder_nr"))]
# It's only set if you explicitly choose an extruder
extruder_id = self.callDecoration("getActiveExtruder")
@@ -72,9 +78,34 @@ class CuraSceneNode(SceneNode):
1.0
]
+ ## Return if the provided bbox collides with the bbox of this scene node
+ def collidesWithBbox(self, check_bbox):
+ bbox = self.getBoundingBox()
+
+ # Mark the node as outside the build volume if the bounding box test fails.
+ if check_bbox.intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection:
+ return True
+
+ return False
+
+ ## Return if any area collides with the convex hull of this scene node
+ def collidesWithArea(self, areas):
+ convex_hull = self.callDecoration("getConvexHull")
+ if convex_hull:
+ if not convex_hull.isValid():
+ return False
+
+ # Check for collisions between disallowed areas and the object
+ for area in areas:
+ overlap = convex_hull.intersectsPolygon(area)
+ if overlap is None:
+ continue
+ return True
+ return False
+
## Taken from SceneNode, but replaced SceneNode with CuraSceneNode
def __deepcopy__(self, memo):
- copy = CuraSceneNode()
+ copy = CuraSceneNode(no_setting_override = True) # Setting override will be added later
copy.setTransformation(self.getLocalTransformation())
copy.setMeshData(self._mesh_data)
copy.setVisible(deepcopy(self._visible, memo))
diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py
index f910f0c0e2..760a288b7b 100644
--- a/cura/Settings/ContainerManager.py
+++ b/cura/Settings/ContainerManager.py
@@ -334,10 +334,13 @@ class ContainerManager(QObject):
# Go through global and extruder stacks and clear their topmost container (the user settings).
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
- container = stack.getTop()
+ container = stack.userChanges
container.clear()
send_emits_containers.append(container)
+ # user changes are possibly added to make the current setup match the current enabled extruders
+ Application.getInstance().getMachineManager().correctExtruderSettings()
+
for container in send_emits_containers:
container.sendPostponedEmits()
@@ -345,15 +348,18 @@ class ContainerManager(QObject):
#
# \param material_id \type{str} the id of the material for which to get the linked materials.
# \return \type{list} a list of names of materials with the same GUID
- @pyqtSlot("QVariant", result = "QStringList")
- def getLinkedMaterials(self, material_node):
+ @pyqtSlot("QVariant", bool, result = "QStringList")
+ def getLinkedMaterials(self, material_node, exclude_self = False):
guid = material_node.metadata["GUID"]
+ self_root_material_id = material_node.metadata["base_file"]
material_group_list = self._material_manager.getMaterialGroupListByGUID(guid)
linked_material_names = []
if material_group_list:
for material_group in material_group_list:
+ if exclude_self and material_group.name == self_root_material_id:
+ continue
linked_material_names.append(material_group.root_material_node.metadata["name"])
return linked_material_names
@@ -389,8 +395,6 @@ class ContainerManager(QObject):
return ContainerManager.getInstance()
def _performMerge(self, merge_into, merge, clear_settings = True):
- assert isinstance(merge, type(merge_into))
-
if merge == merge_into:
return
diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py
index e79cfa5335..ab48eaddd2 100644
--- a/cura/Settings/CuraContainerRegistry.py
+++ b/cura/Settings/CuraContainerRegistry.py
@@ -29,6 +29,7 @@ from .ExtruderManager import ExtruderManager
from cura.CuraApplication import CuraApplication
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
+from cura.ProfileReader import NoProfileException
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
@@ -172,12 +173,13 @@ class CuraContainerRegistry(ContainerRegistry):
plugin_registry = PluginRegistry.getInstance()
extension = file_name.split(".")[-1]
- global_container_stack = Application.getInstance().getGlobalContainerStack()
- if not global_container_stack:
+ global_stack = Application.getInstance().getGlobalContainerStack()
+ if not global_stack:
return
- machine_extruders = list(ExtruderManager.getInstance().getMachineExtruders(global_container_stack.getId()))
- machine_extruders.sort(key = lambda k: k.getMetaDataEntry("position"))
+ machine_extruders = []
+ for position in sorted(global_stack.extruders):
+ machine_extruders.append(global_stack.extruders[position])
for plugin_id, meta_data in self._getIOPlugins("profile_reader"):
if meta_data["profile_reader"][0]["extension"] != extension:
@@ -185,6 +187,8 @@ class CuraContainerRegistry(ContainerRegistry):
profile_reader = plugin_registry.getPluginObject(plugin_id)
try:
profile_or_list = profile_reader.read(file_name) # Try to open the file with the profile reader.
+ except NoProfileException:
+ return { "status": "ok", "message": catalog.i18nc("@info:status Don't translate the XML tags or !", "No custom profile to import in file {0} ", file_name)}
except Exception as e:
# Note that this will fail quickly. That is, if any profile reader throws an exception, it will stop reading. It will only continue reading if the reader returned None.
Logger.log("e", "Failed to import profile from %s: %s while using profile reader. Got exception %s", file_name,profile_reader.getPluginId(), str(e))
@@ -197,28 +201,51 @@ class CuraContainerRegistry(ContainerRegistry):
# First check if this profile is suitable for this machine
global_profile = None
+ extruder_profiles = []
if len(profile_or_list) == 1:
global_profile = profile_or_list[0]
else:
for profile in profile_or_list:
- if not profile.getMetaDataEntry("extruder"):
+ if not profile.getMetaDataEntry("position"):
global_profile = profile
- break
+ else:
+ extruder_profiles.append(profile)
+ extruder_profiles = sorted(extruder_profiles, key = lambda x: int(x.getMetaDataEntry("position")))
+ profile_or_list = [global_profile] + extruder_profiles
+
if not global_profile:
Logger.log("e", "Incorrect profile [%s]. Could not find global profile", file_name)
return { "status": "error",
"message": catalog.i18nc("@info:status Don't translate the XML tags or !", "This profile {0} contains incorrect data, could not import it.", file_name)}
profile_definition = global_profile.getMetaDataEntry("definition")
- expected_machine_definition = "fdmprinter"
- if parseBool(global_container_stack.getMetaDataEntry("has_machine_quality", "False")):
- expected_machine_definition = global_container_stack.getMetaDataEntry("quality_definition")
- if not expected_machine_definition:
- expected_machine_definition = global_container_stack.definition.getId()
- if expected_machine_definition is not None and profile_definition is not None and profile_definition != expected_machine_definition:
+
+ # Make sure we have a profile_definition in the file:
+ if profile_definition is None:
+ break
+ machine_definition = self.findDefinitionContainers(id = profile_definition)
+ if not machine_definition:
+ Logger.log("e", "Incorrect profile [%s]. Unknown machine type [%s]", file_name, profile_definition)
+ return {"status": "error",
+ "message": catalog.i18nc("@info:status Don't translate the XML tags or !", "This profile {0} contains incorrect data, could not import it.", file_name)
+ }
+ machine_definition = machine_definition[0]
+
+ # Get the expected machine definition.
+ # i.e.: We expect gcode for a UM2 Extended to be defined as normal UM2 gcode...
+ profile_definition = getMachineDefinitionIDForQualitySearch(machine_definition)
+ expected_machine_definition = getMachineDefinitionIDForQualitySearch(global_stack.definition)
+
+ # And check if the profile_definition matches either one (showing error if not):
+ if profile_definition != expected_machine_definition:
Logger.log("e", "Profile [%s] is for machine [%s] but the current active machine is [%s]. Will not import the profile", file_name, profile_definition, expected_machine_definition)
return { "status": "error",
"message": catalog.i18nc("@info:status Don't translate the XML tags or !", "The machine defined in profile {0} ({1}) doesn't match with your current machine ({2}), could not import it.", file_name, profile_definition, expected_machine_definition)}
+ # Fix the global quality profile's definition field in case it's not correct
+ global_profile.setMetaDataEntry("definition", expected_machine_definition)
+ quality_name = global_profile.getName()
+ quality_type = global_profile.getMetaDataEntry("quality_type")
+
name_seed = os.path.splitext(os.path.basename(file_name))[0]
new_name = self.uniqueName(name_seed)
@@ -230,25 +257,25 @@ class CuraContainerRegistry(ContainerRegistry):
if len(profile_or_list) == 1:
global_profile = profile_or_list[0]
extruder_profiles = []
- for idx, extruder in enumerate(global_container_stack.extruders.values()):
- profile_id = ContainerRegistry.getInstance().uniqueName(global_container_stack.getId() + "_extruder_" + str(idx + 1))
+ for idx, extruder in enumerate(global_stack.extruders.values()):
+ profile_id = ContainerRegistry.getInstance().uniqueName(global_stack.getId() + "_extruder_" + str(idx + 1))
profile = InstanceContainer(profile_id)
- profile.setName(global_profile.getName())
+ profile.setName(quality_name)
profile.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
profile.addMetaDataEntry("type", "quality_changes")
- profile.addMetaDataEntry("definition", global_profile.getMetaDataEntry("definition"))
- profile.addMetaDataEntry("quality_type", global_profile.getMetaDataEntry("quality_type"))
- profile.addMetaDataEntry("extruder", extruder.getId())
+ profile.addMetaDataEntry("definition", expected_machine_definition)
+ profile.addMetaDataEntry("quality_type", quality_type)
+ profile.addMetaDataEntry("position", "0")
profile.setDirty(True)
if idx == 0:
# move all per-extruder settings to the first extruder's quality_changes
for qc_setting_key in global_profile.getAllKeys():
- settable_per_extruder = global_container_stack.getProperty(qc_setting_key,
+ settable_per_extruder = global_stack.getProperty(qc_setting_key,
"settable_per_extruder")
if settable_per_extruder:
setting_value = global_profile.getProperty(qc_setting_key, "value")
- setting_definition = global_container_stack.getSettingDefinition(qc_setting_key)
+ setting_definition = global_stack.getSettingDefinition(qc_setting_key)
new_instance = SettingInstance(setting_definition, profile)
new_instance.setProperty("value", setting_value)
new_instance.resetState() # Ensure that the state is not seen as a user state.
@@ -265,21 +292,22 @@ class CuraContainerRegistry(ContainerRegistry):
for profile_index, profile in enumerate(profile_or_list):
if profile_index == 0:
# This is assumed to be the global profile
- profile_id = (global_container_stack.getBottom().getId() + "_" + name_seed).lower().replace(" ", "_")
+ profile_id = (global_stack.getBottom().getId() + "_" + name_seed).lower().replace(" ", "_")
elif profile_index < len(machine_extruders) + 1:
# This is assumed to be an extruder profile
extruder_id = machine_extruders[profile_index - 1].definition.getId()
- if not profile.getMetaDataEntry("extruder"):
- profile.addMetaDataEntry("extruder", extruder_id)
+ extruder_position = str(profile_index - 1)
+ if not profile.getMetaDataEntry("position"):
+ profile.addMetaDataEntry("position", extruder_position)
else:
- profile.setMetaDataEntry("extruder", extruder_id)
+ profile.setMetaDataEntry("position", extruder_position)
profile_id = (extruder_id + "_" + name_seed).lower().replace(" ", "_")
else: #More extruders in the imported file than in the machine.
continue #Delete the additional profiles.
- result = self._configureProfile(profile, profile_id, new_name)
+ result = self._configureProfile(profile, profile_id, new_name, expected_machine_definition)
if result is not None:
return {"status": "error", "message": catalog.i18nc(
"@info:status Don't translate the XML tags or !",
@@ -307,7 +335,7 @@ class CuraContainerRegistry(ContainerRegistry):
# \param new_name The new name for the profile.
#
# \return None if configuring was successful or an error message if an error occurred.
- def _configureProfile(self, profile: InstanceContainer, id_seed: str, new_name: str) -> Optional[str]:
+ def _configureProfile(self, profile: InstanceContainer, id_seed: str, new_name: str, machine_definition_id: str) -> Optional[str]:
profile.setDirty(True) # Ensure the profiles are correctly saved
new_id = self.createUniqueName("quality_changes", "", id_seed, catalog.i18nc("@label", "Custom profile"))
@@ -317,6 +345,7 @@ class CuraContainerRegistry(ContainerRegistry):
# Set the unique Id to the profile, so it's generating a new one even if the user imports the same profile
# It also solves an issue with importing profiles from G-Codes
profile.setMetaDataEntry("id", new_id)
+ profile.setMetaDataEntry("definition", machine_definition_id)
if "type" in profile.getMetaData():
profile.setMetaDataEntry("type", "quality_changes")
@@ -327,9 +356,8 @@ class CuraContainerRegistry(ContainerRegistry):
if not quality_type:
return catalog.i18nc("@info:status", "Profile is missing a quality type.")
- quality_type_criteria = {"quality_type": quality_type}
global_stack = Application.getInstance().getGlobalContainerStack()
- definition_id = getMachineDefinitionIDForQualitySearch(global_stack)
+ definition_id = getMachineDefinitionIDForQualitySearch(global_stack.definition)
profile.setDefinition(definition_id)
# Check to make sure the imported profile actually makes sense in context of the current configuration.
diff --git a/cura/Settings/CuraContainerStack.py b/cura/Settings/CuraContainerStack.py
index 00db4f57c7..ca4f866598 100755
--- a/cura/Settings/CuraContainerStack.py
+++ b/cura/Settings/CuraContainerStack.py
@@ -8,6 +8,7 @@ from typing import Any, Optional
from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject
from UM.FlameProfiler import pyqtSlot
+from UM.Application import Application
from UM.Decorators import override
from UM.Logger import Logger
from UM.Settings.ContainerStack import ContainerStack, InvalidContainerStackError
@@ -314,6 +315,13 @@ class CuraContainerStack(ContainerStack):
return cls._findInstanceContainerDefinitionId(definitions[0])
+ ## getProperty for extruder positions, with translation from -1 to default extruder number
+ def getExtruderPositionValueWithDefault(self, key):
+ value = self.getProperty(key, "value")
+ if value == -1:
+ value = int(Application.getInstance().getMachineManager().defaultExtruderPosition)
+ return value
+
## private:
# Private helper class to keep track of container positions and their types.
diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py
index 06342b68e5..2b422ec406 100755
--- a/cura/Settings/ExtruderManager.py
+++ b/cura/Settings/ExtruderManager.py
@@ -241,6 +241,13 @@ class ExtruderManager(QObject):
result.append(extruder_stack.getProperty(setting_key, prop))
return result
+ def extruderValueWithDefault(self, value):
+ machine_manager = self._application.getMachineManager()
+ if value == "-1":
+ return machine_manager.defaultExtruderPosition
+ else:
+ return value
+
## Gets the extruder stacks that are actually being used at the moment.
#
# An extruder stack is being used if it is the extruder to print any mesh
@@ -252,7 +259,7 @@ class ExtruderManager(QObject):
#
# \return A list of extruder stacks.
def getUsedExtruderStacks(self) -> List["ContainerStack"]:
- global_stack = Application.getInstance().getGlobalContainerStack()
+ global_stack = self._application.getGlobalContainerStack()
container_registry = ContainerRegistry.getInstance()
used_extruder_stack_ids = set()
@@ -302,16 +309,19 @@ class ExtruderManager(QObject):
# Check support extruders
if support_enabled:
- used_extruder_stack_ids.add(self.extruderIds[str(global_stack.getProperty("support_infill_extruder_nr", "value"))])
- used_extruder_stack_ids.add(self.extruderIds[str(global_stack.getProperty("support_extruder_nr_layer_0", "value"))])
+ used_extruder_stack_ids.add(self.extruderIds[self.extruderValueWithDefault(str(global_stack.getProperty("support_infill_extruder_nr", "value")))])
+ used_extruder_stack_ids.add(self.extruderIds[self.extruderValueWithDefault(str(global_stack.getProperty("support_extruder_nr_layer_0", "value")))])
if support_bottom_enabled:
- used_extruder_stack_ids.add(self.extruderIds[str(global_stack.getProperty("support_bottom_extruder_nr", "value"))])
+ used_extruder_stack_ids.add(self.extruderIds[self.extruderValueWithDefault(str(global_stack.getProperty("support_bottom_extruder_nr", "value")))])
if support_roof_enabled:
- used_extruder_stack_ids.add(self.extruderIds[str(global_stack.getProperty("support_roof_extruder_nr", "value"))])
+ used_extruder_stack_ids.add(self.extruderIds[self.extruderValueWithDefault(str(global_stack.getProperty("support_roof_extruder_nr", "value")))])
# The platform adhesion extruder. Not used if using none.
if global_stack.getProperty("adhesion_type", "value") != "none":
- used_extruder_stack_ids.add(self.extruderIds[str(global_stack.getProperty("adhesion_extruder_nr", "value"))])
+ extruder_nr = str(global_stack.getProperty("adhesion_extruder_nr", "value"))
+ if extruder_nr == "-1":
+ extruder_nr = Application.getInstance().getMachineManager().defaultExtruderPosition
+ used_extruder_stack_ids.add(self.extruderIds[extruder_nr])
try:
return [container_registry.findContainerStacks(id = stack_id)[0] for stack_id in used_extruder_stack_ids]
@@ -485,6 +495,8 @@ class ExtruderManager(QObject):
result = []
for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()):
+ if not extruder.isEnabled:
+ continue
# 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
@@ -656,6 +668,8 @@ class ExtruderManager(QObject):
# global stack if not found.
@staticmethod
def getExtruderValue(extruder_index, key):
+ if extruder_index == -1:
+ extruder_index = int(Application.getInstance().getMachineManager().defaultExtruderPosition)
extruder = ExtruderManager.getInstance().getExtruderStack(extruder_index)
if extruder:
diff --git a/cura/Settings/ExtruderStack.py b/cura/Settings/ExtruderStack.py
index 4400f621c6..8dcaaf302e 100644
--- a/cura/Settings/ExtruderStack.py
+++ b/cura/Settings/ExtruderStack.py
@@ -3,13 +3,15 @@
from typing import Any, TYPE_CHECKING, Optional
-from PyQt5.QtCore import pyqtProperty
+from PyQt5.QtCore import pyqtProperty, pyqtSignal
+from UM.Application import Application
from UM.Decorators import override
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.Interfaces import ContainerInterface, PropertyEvaluationContext
+from UM.Util import parseBool
from . import Exceptions
from .CuraContainerStack import CuraContainerStack, _ContainerIndexes
@@ -30,6 +32,8 @@ class ExtruderStack(CuraContainerStack):
self.propertiesChanged.connect(self._onPropertiesChanged)
+ enabledChanged = pyqtSignal()
+
## Overridden from ContainerStack
#
# This will set the next stack and ensure that we register this stack as an extruder.
@@ -46,6 +50,16 @@ class ExtruderStack(CuraContainerStack):
def getNextStack(self) -> Optional["GlobalStack"]:
return super().getNextStack()
+ def setEnabled(self, enabled):
+ if "enabled" not in self._metadata:
+ self.addMetaDataEntry("enabled", "True")
+ self.setMetaDataEntry("enabled", str(enabled))
+ self.enabledChanged.emit()
+
+ @pyqtProperty(bool, notify = enabledChanged)
+ def isEnabled(self):
+ return parseBool(self.getMetaDataEntry("enabled", "True"))
+
@classmethod
def getLoadingPriority(cls) -> int:
return 3
@@ -98,6 +112,8 @@ class ExtruderStack(CuraContainerStack):
limit_to_extruder = super().getProperty(key, "limit_to_extruder", context)
if limit_to_extruder is not None:
+ if limit_to_extruder == -1:
+ limit_to_extruder = int(Application.getInstance().getMachineManager().defaultExtruderPosition)
limit_to_extruder = str(limit_to_extruder)
if (limit_to_extruder is not None and limit_to_extruder != "-1") and self.getMetaDataEntry("position") != str(limit_to_extruder):
if str(limit_to_extruder) in self.getNextStack().extruders:
@@ -120,6 +136,8 @@ class ExtruderStack(CuraContainerStack):
@override(CuraContainerStack)
def deserialize(self, contents: str, file_name: Optional[str] = None) -> None:
super().deserialize(contents, file_name)
+ if "enabled" not in self.getMetaData():
+ self.addMetaDataEntry("enabled", "True")
stacks = ContainerRegistry.getInstance().findContainerStacks(id=self.getMetaDataEntry("machine", ""))
if stacks:
self.setNextStack(stacks[0])
diff --git a/cura/Settings/ExtrudersModel.py b/cura/Settings/ExtrudersModel.py
index 5139b9885d..f179dabd5a 100644
--- a/cura/Settings/ExtrudersModel.py
+++ b/cura/Settings/ExtrudersModel.py
@@ -1,7 +1,7 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty, QTimer
+from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot, pyqtProperty, QTimer
from typing import Iterable
from UM.i18n import i18nCatalog
@@ -24,6 +24,8 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
## Human-readable name of the extruder.
NameRole = Qt.UserRole + 2
+ ## Is the extruder enabled?
+ EnabledRole = Qt.UserRole + 9
## Colour of the material loaded in the extruder.
ColorRole = Qt.UserRole + 3
@@ -43,6 +45,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
# The variant of the extruder.
VariantRole = Qt.UserRole + 7
+ StackRole = Qt.UserRole + 8
## List of colours to display if there is no material or the material has no known
# colour.
@@ -57,11 +60,13 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
self.addRoleName(self.IdRole, "id")
self.addRoleName(self.NameRole, "name")
+ self.addRoleName(self.EnabledRole, "enabled")
self.addRoleName(self.ColorRole, "color")
self.addRoleName(self.IndexRole, "index")
self.addRoleName(self.DefinitionRole, "definition")
self.addRoleName(self.MaterialRole, "material")
self.addRoleName(self.VariantRole, "variant")
+ self.addRoleName(self.StackRole, "stack")
self._update_extruder_timer = QTimer()
self._update_extruder_timer.setInterval(100)
@@ -183,11 +188,13 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
item = {
"id": extruder.getId(),
"name": extruder.getName(),
+ "enabled": extruder.isEnabled,
"color": color,
"index": position,
"definition": extruder.getBottom().getId(),
"material": extruder.material.getName() if extruder.material else "",
"variant": extruder.variant.getName() if extruder.variant else "", # e.g. print core
+ "stack": extruder,
}
items.append(item)
@@ -203,6 +210,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
item = {
"id": "",
"name": catalog.i18nc("@menuitem", "Not overridden"),
+ "enabled": True,
"color": "#ffffff",
"index": -1,
"definition": ""
diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py
index ae1f1370ed..5d8a4505a5 100755
--- a/cura/Settings/GlobalStack.py
+++ b/cura/Settings/GlobalStack.py
@@ -7,6 +7,7 @@ from typing import Any, Dict, Optional
from PyQt5.QtCore import pyqtProperty
+from UM.Application import Application
from UM.Decorators import override
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
@@ -104,6 +105,8 @@ class GlobalStack(CuraContainerStack):
# Handle the "limit_to_extruder" property.
limit_to_extruder = super().getProperty(key, "limit_to_extruder", context)
if limit_to_extruder is not None:
+ if limit_to_extruder == -1:
+ limit_to_extruder = int(Application.getInstance().getMachineManager().defaultExtruderPosition)
limit_to_extruder = str(limit_to_extruder)
if limit_to_extruder is not None and limit_to_extruder != "-1" and limit_to_extruder in self._extruders:
if super().getProperty(key, "settable_per_extruder", context):
diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py
index e357d778ca..54abaca86e 100755
--- a/cura/Settings/MachineManager.py
+++ b/cura/Settings/MachineManager.py
@@ -4,13 +4,12 @@
import collections
import time
#Type hinting.
-from typing import Union, List, Dict, TYPE_CHECKING, Optional
+from typing import List, Dict, TYPE_CHECKING, Optional
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Signal import Signal
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, QTimer
-import UM.FlameProfiler
from UM.FlameProfiler import pyqtSlot
from UM import Util
@@ -20,13 +19,14 @@ from UM.Logger import Logger
from UM.Message import Message
from UM.Settings.ContainerRegistry import ContainerRegistry
-from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.SettingFunction import SettingFunction
from UM.Signal import postponeSignals, CompressTechnique
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
-
from cura.PrinterOutputDevice import PrinterOutputDevice
+from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
+from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel
+from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
from cura.Settings.ExtruderManager import ExtruderManager
from .CuraStackBuilder import CuraStackBuilder
@@ -48,16 +48,12 @@ class MachineManager(QObject):
self._global_container_stack = None # type: GlobalStack
self._current_root_material_id = {}
- self._current_root_material_name = {}
self._current_quality_group = None
self._current_quality_changes_group = None
- self.machine_extruder_material_update_dict = collections.defaultdict(list)
+ self._default_extruder_position = "0" # to be updated when extruders are switched on and off
- 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.machine_extruder_material_update_dict = collections.defaultdict(list)
self._instance_container_timer = QTimer()
self._instance_container_timer.setInterval(250)
@@ -107,6 +103,12 @@ class MachineManager(QObject):
# There might already be some output devices by the time the signal is connected
self._onOutputDevicesChanged()
+ self._current_printer_configuration = ConfigurationModel() # Indicates the current configuration setup in this printer
+ self.activeMaterialChanged.connect(self._onCurrentConfigurationChanged)
+ self.activeVariantChanged.connect(self._onCurrentConfigurationChanged)
+ # Force to compute the current configuration
+ self._onCurrentConfigurationChanged()
+
self._application.callLater(self.setInitialActiveMachine)
self._material_incompatible_message = Message(catalog.i18nc("@info:status",
@@ -117,12 +119,17 @@ class MachineManager(QObject):
if containers:
containers[0].nameChanged.connect(self._onMaterialNameChanged)
- self._material_manager = self._application._material_manager
+ self._material_manager = self._application.getMaterialManager()
+ self._variant_manager = self._application.getVariantManager()
self._quality_manager = self._application.getQualityManager()
# When the materials lookup table gets updated, it can mean that a material has its name changed, which should
# be reflected on the GUI. This signal emission makes sure that it happens.
self._material_manager.materialsUpdated.connect(self.rootMaterialChanged)
+ # When the materials get updated, it can be that an activated material's diameter gets changed. In that case,
+ # a material update should be triggered to make sure that the machine still has compatible materials activated.
+ self._material_manager.materialsUpdated.connect(self._updateUponMaterialMetadataChange)
+ self.rootMaterialChanged.connect(self._onRootMaterialChanged)
activeQualityGroupChanged = pyqtSignal()
activeQualityChangesGroupChanged = pyqtSignal()
@@ -132,15 +139,18 @@ class MachineManager(QObject):
activeVariantChanged = pyqtSignal()
activeQualityChanged = pyqtSignal()
activeStackChanged = pyqtSignal() # Emitted whenever the active stack is changed (ie: when changing between extruders, changing a profile, but not when changing a value)
+ extruderChanged = pyqtSignal()
globalValueChanged = pyqtSignal() # Emitted whenever a value inside global container is changed.
activeStackValueChanged = pyqtSignal() # Emitted whenever a value inside the active stack is changed.
activeStackValidationChanged = pyqtSignal() # Emitted whenever a validation inside active container is changed
stacksValidationChanged = pyqtSignal() # Emitted whenever a validation is changed
+ numberExtrudersEnabledChanged = pyqtSignal() # Emitted when the number of extruders that are enabled changed
blurSettings = pyqtSignal() # Emitted to force fields in the advanced sidebar to un-focus, so they update properly
outputDevicesChanged = pyqtSignal()
+ currentConfigurationChanged = pyqtSignal() # Emitted every time the current configurations of the machine changes
rootMaterialChanged = pyqtSignal()
@@ -160,6 +170,39 @@ class MachineManager(QObject):
self.outputDevicesChanged.emit()
+ @pyqtProperty(QObject, notify = currentConfigurationChanged)
+ def currentConfiguration(self):
+ return self._current_printer_configuration
+
+ def _onCurrentConfigurationChanged(self) -> None:
+ if not self._global_container_stack:
+ return
+
+ # Create the configuration model with the current data in Cura
+ self._current_printer_configuration.printerType = self._global_container_stack.definition.getName()
+ self._current_printer_configuration.extruderConfigurations = []
+ for extruder in self._global_container_stack.extruders.values():
+ extruder_configuration = ExtruderConfigurationModel()
+ # For compare just the GUID is needed at this moment
+ mat_type = extruder.material.getMetaDataEntry("material") if extruder.material != self._empty_material_container else None
+ mat_guid = extruder.material.getMetaDataEntry("GUID") if extruder.material != self._empty_material_container else None
+ mat_color = extruder.material.getMetaDataEntry("color_name") if extruder.material != self._empty_material_container else None
+ mat_brand = extruder.material.getMetaDataEntry("brand") if extruder.material != self._empty_material_container else None
+ mat_name = extruder.material.getMetaDataEntry("name") if extruder.material != self._empty_material_container else None
+ material_model = MaterialOutputModel(mat_guid, mat_type, mat_color, mat_brand, mat_name)
+
+ extruder_configuration.position = int(extruder.getMetaDataEntry("position"))
+ extruder_configuration.material = material_model
+ extruder_configuration.hotendID = extruder.variant.getName() if extruder.variant != self._empty_variant_container else None
+ self._current_printer_configuration.extruderConfigurations.append(extruder_configuration)
+
+ self._current_printer_configuration.buildplateConfiguration = self._global_container_stack.getProperty("machine_buildplate_type", "value") if self._global_container_stack.variant != self._empty_variant_container else None
+ self.currentConfigurationChanged.emit()
+
+ @pyqtSlot(QObject, result = bool)
+ def matchesConfiguration(self, configuration: ConfigurationModel) -> bool:
+ return self._current_printer_configuration == configuration
+
@pyqtProperty("QVariantList", notify = outputDevicesChanged)
def printerOutputDevices(self):
return self._printer_output_devices
@@ -189,7 +232,9 @@ class MachineManager(QObject):
# Update the local global container stack reference
self._global_container_stack = Application.getInstance().getGlobalContainerStack()
-
+ if self._global_container_stack:
+ self.updateDefaultExtruder()
+ self.updateNumberExtrudersEnabled()
self.globalContainerChanged.emit()
# after switching the global stack we reconnect all the signals and set the variant and material references
@@ -222,15 +267,6 @@ class MachineManager(QObject):
del self.machine_extruder_material_update_dict[self._global_container_stack.getId()]
self.activeQualityGroupChanged.emit()
- self._error_check_timer.start()
-
- ## Update self._stacks_valid according to _checkStacksForErrors and emit if change.
- def _updateStacksHaveErrors(self) -> None:
- old_stacks_have_errors = self._stacks_have_errors
- self._stacks_have_errors = self._checkStacksHaveErrors()
- if old_stacks_have_errors != self._stacks_have_errors:
- self.stacksValidationChanged.emit()
- Application.getInstance().stacksValidationFinished.emit()
def _onActiveExtruderStackChanged(self) -> None:
self.blurSettings.emit() # Ensure no-one has focus.
@@ -250,8 +286,6 @@ class MachineManager(QObject):
self.rootMaterialChanged.emit()
- self._error_check_timer.start()
-
def _onInstanceContainersChanged(self, container) -> None:
self._instance_container_timer.start()
@@ -260,9 +294,6 @@ class MachineManager(QObject):
# Notify UI items, such as the "changed" star in profile pull down menu.
self.activeStackValueChanged.emit()
- elif property_name == "validationState":
- self._error_check_timer.start()
-
## Given a global_stack, make sure that it's all valid by searching for this quality group and applying it again
def _initMachineState(self, global_stack):
material_dict = {}
@@ -306,6 +337,18 @@ class MachineManager(QObject):
self.__emitChangedSignals()
+ ## Given a definition id, return the machine with this id.
+ # Optional: add a list of keys and values to filter the list of machines with the given definition id
+ # \param definition_id \type{str} definition id that needs to look for
+ # \param metadata_filter \type{dict} list of metadata keys and values used for filtering
+ @staticmethod
+ def getMachine(definition_id: str, metadata_filter: Dict[str, str] = None) -> Optional["GlobalStack"]:
+ machines = ContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
+ for machine in machines:
+ if machine.definition.getId() == definition_id:
+ return machine
+ return None
+
@pyqtSlot(str, str)
def addMachine(self, name: str, definition_id: str) -> None:
new_stack = CuraStackBuilder.createMachine(name, definition_id)
@@ -321,7 +364,7 @@ class MachineManager(QObject):
return False
if self._global_container_stack.hasErrors():
- Logger.log("d", "Checking global stack for errors took %0.2f s and we found and error" % (time.time() - time_start))
+ Logger.log("d", "Checking global stack for errors took %0.2f s and we found an error" % (time.time() - time_start))
return True
# Not a very pretty solution, but the extruder manager doesn't really know how many extruders there are
@@ -405,6 +448,12 @@ class MachineManager(QObject):
def stacksHaveErrors(self) -> bool:
return bool(self._stacks_have_errors)
+ @pyqtProperty(str, notify = globalContainerChanged)
+ def activeMachineDefinitionName(self) -> str:
+ if self._global_container_stack:
+ return self._global_container_stack.definition.getName()
+ return ""
+
@pyqtProperty(str, notify = globalContainerChanged)
def activeMachineName(self) -> str:
if self._global_container_stack:
@@ -417,6 +466,18 @@ class MachineManager(QObject):
return self._global_container_stack.getId()
return ""
+ @pyqtProperty(str, notify = outputDevicesChanged)
+ def activeMachineNetworkKey(self) -> str:
+ if self._global_container_stack:
+ return self._global_container_stack.getMetaDataEntry("um_network_key", "")
+ return ""
+
+ @pyqtProperty(str, notify = outputDevicesChanged)
+ def activeMachineNetworkGroupName(self) -> str:
+ if self._global_container_stack:
+ return self._global_container_stack.getMetaDataEntry("connect_group_name", "")
+ return ""
+
@pyqtProperty(QObject, notify = globalContainerChanged)
def activeMachine(self) -> Optional["GlobalStack"]:
return self._global_container_stack
@@ -566,7 +627,7 @@ class MachineManager(QObject):
@pyqtProperty(str, notify = globalContainerChanged)
def activeQualityDefinitionId(self) -> str:
if self._global_container_stack:
- return getMachineDefinitionIDForQualitySearch(self._global_container_stack)
+ return getMachineDefinitionIDForQualitySearch(self._global_container_stack.definition)
return ""
## Gets how the active definition calls variants
@@ -600,12 +661,22 @@ class MachineManager(QObject):
if other_machine_stacks:
self.setActiveMachine(other_machine_stacks[0]["id"])
+ metadata = ContainerRegistry.getInstance().findContainerStacksMetadata(id = machine_id)[0]
+ network_key = metadata["um_network_key"] if "um_network_key" in metadata else None
ExtruderManager.getInstance().removeMachineExtruders(machine_id)
containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(type = "user", machine = machine_id)
for container in containers:
ContainerRegistry.getInstance().removeContainer(container["id"])
ContainerRegistry.getInstance().removeContainer(machine_id)
+ # If the printer that is being removed is a network printer, the hidden printers have to be also removed
+ if network_key:
+ metadata_filter = {"um_network_key": network_key}
+ hidden_containers = ContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
+ if hidden_containers:
+ # This reuses the method and remove all printers recursively
+ self.removeMachine(hidden_containers[0].getId())
+
@pyqtProperty(bool, notify = globalContainerChanged)
def hasMaterials(self) -> bool:
if self._global_container_stack:
@@ -633,6 +704,8 @@ class MachineManager(QObject):
buildplate_compatible = True # It is compatible by default
extruder_stacks = self._global_container_stack.extruders.values()
for stack in extruder_stacks:
+ if not stack.isEnabled:
+ continue
material_container = stack.material
if material_container == self._empty_material_container:
continue
@@ -665,22 +738,6 @@ class MachineManager(QObject):
return result
- ## Property to indicate if a machine has "specialized" material profiles.
- # Some machines have their own material profiles that "override" the default catch all profiles.
- @pyqtProperty(bool, notify = globalContainerChanged)
- def filterMaterialsByMachine(self) -> bool:
- if self._global_container_stack:
- return Util.parseBool(self._global_container_stack.getMetaDataEntry("has_machine_materials", False))
- return False
-
- ## Property to indicate if a machine has "specialized" quality profiles.
- # Some machines have their own quality profiles that "override" the default catch all profiles.
- @pyqtProperty(bool, notify = globalContainerChanged)
- def filterQualityByMachine(self) -> bool:
- if self._global_container_stack:
- return Util.parseBool(self._global_container_stack.getMetaDataEntry("has_machine_quality", False))
- return False
-
## Get the Definition ID of a machine (specified by ID)
# \param machine_id string machine id to get the definition ID of
# \returns DefinitionID (string) if found, None otherwise
@@ -690,6 +747,43 @@ class MachineManager(QObject):
if containers:
return containers[0].definition.getId()
+ def getIncompatibleSettingsOnEnabledExtruders(self, container):
+ extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
+ result = []
+ for setting_instance in container.findInstances():
+ setting_key = setting_instance.definition.key
+ setting_enabled = self._global_container_stack.getProperty(setting_key, "enabled")
+ if not setting_enabled:
+ # A setting is not visible anymore
+ result.append(setting_key)
+ Logger.log("d", "Reset setting [%s] from [%s] because the setting is no longer enabled", setting_key, container)
+ continue
+
+ if not self._global_container_stack.getProperty(setting_key, "type") in ("extruder", "optional_extruder"):
+ continue
+
+ old_value = container.getProperty(setting_key, "value")
+ if int(old_value) >= extruder_count or not self._global_container_stack.extruders[str(old_value)].isEnabled:
+ result.append(setting_key)
+ Logger.log("d", "Reset setting [%s] in [%s] because its old value [%s] is no longer valid", setting_key, container, old_value)
+ return result
+
+ ## Update extruder number to a valid value when the number of extruders are changed, or when an extruder is changed
+ def correctExtruderSettings(self):
+ for setting_key in self.getIncompatibleSettingsOnEnabledExtruders(self._global_container_stack.userChanges):
+ self._global_container_stack.userChanges.removeInstance(setting_key)
+ add_user_changes = self.getIncompatibleSettingsOnEnabledExtruders(self._global_container_stack.qualityChanges)
+ for setting_key in add_user_changes:
+ # Apply quality changes that are incompatible to user changes, so we do not change the quality changes itself.
+ self._global_container_stack.userChanges.setProperty(setting_key, "value", self._default_extruder_position)
+ if add_user_changes:
+ caution_message = Message(catalog.i18nc(
+ "@info:generic",
+ "Settings have been changed to match the current availability of extruders: [%s]" % ", ".join(add_user_changes)),
+ lifetime=0,
+ title = catalog.i18nc("@info:title", "Settings updated"))
+ caution_message.show()
+
## Set the amount of extruders on the active machine (global stack)
# \param extruder_count int the number of extruders to set
def setActiveMachineExtruderCount(self, extruder_count):
@@ -703,16 +797,11 @@ class MachineManager(QObject):
if extruder_count == previous_extruder_count:
return
- # reset all extruder number settings whose value is no longer valid
- for setting_instance in self._global_container_stack.userChanges.findInstances():
- setting_key = setting_instance.definition.key
- if not self._global_container_stack.getProperty(setting_key, "type") in ("extruder", "optional_extruder"):
- continue
+ definition_changes_container.setProperty("machine_extruder_count", "value", extruder_count)
- old_value = int(self._global_container_stack.userChanges.getProperty(setting_key, "value"))
- if old_value >= extruder_count:
- self._global_container_stack.userChanges.removeInstance(setting_key)
- Logger.log("d", "Reset [%s] because its old value [%s] is no longer valid ", setting_key, old_value)
+ self.updateDefaultExtruder()
+ self.updateNumberExtrudersEnabled()
+ self.correctExtruderSettings()
# 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()
@@ -723,21 +812,19 @@ class MachineManager(QObject):
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)
-
# Make sure one of the extruder stacks is active
extruder_manager.setActiveExtruderIndex(0)
# Move settable_per_extruder values out of the global container
# After CURA-4482 this should not be the case anymore, but we still want to support older project files.
- global_user_container = self._global_container_stack.getTop()
+ global_user_container = self._global_container_stack.userChanges
# Make sure extruder_stacks exists
extruder_stacks = []
if previous_extruder_count == 1:
extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
- global_user_container = self._global_container_stack.getTop()
+ global_user_container = self._global_container_stack.userChanges
for setting_instance in global_user_container.findInstances():
setting_key = setting_instance.definition.key
@@ -746,11 +833,12 @@ class MachineManager(QObject):
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"))
+ extruder_stack.userChanges.setProperty(setting_key, "value", global_user_container.getProperty(setting_key, "value"))
global_user_container.removeInstance(setting_key)
# Signal that the global stack has changed
Application.getInstance().globalContainerStackChanged.emit()
+ self.forceUpdateAllSettings()
@pyqtSlot(int, result = QObject)
def getExtruder(self, position: int):
@@ -759,6 +847,63 @@ class MachineManager(QObject):
extruder = self._global_container_stack.extruders.get(str(position))
return extruder
+ def updateDefaultExtruder(self):
+ extruder_items = sorted(self._global_container_stack.extruders.items())
+ old_position = self._default_extruder_position
+ new_default_position = "0"
+ for position, extruder in extruder_items:
+ if extruder.isEnabled:
+ new_default_position = position
+ break
+ if new_default_position != old_position:
+ self._default_extruder_position = new_default_position
+ self.extruderChanged.emit()
+
+ def updateNumberExtrudersEnabled(self):
+ definition_changes_container = self._global_container_stack.definitionChanges
+ machine_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
+ extruder_count = 0
+ for position, extruder in self._global_container_stack.extruders.items():
+ if extruder.isEnabled and int(position) < machine_extruder_count:
+ extruder_count += 1
+ if self.numberExtrudersEnabled != extruder_count:
+ definition_changes_container.setProperty("extruders_enabled_count", "value", extruder_count)
+ self.numberExtrudersEnabledChanged.emit()
+
+ @pyqtProperty(int, notify = numberExtrudersEnabledChanged)
+ def numberExtrudersEnabled(self):
+ return self._global_container_stack.definitionChanges.getProperty("extruders_enabled_count", "value")
+
+ @pyqtProperty(str, notify = extruderChanged)
+ def defaultExtruderPosition(self):
+ return self._default_extruder_position
+
+ ## This will fire the propertiesChanged for all settings so they will be updated in the front-end
+ @pyqtSlot()
+ def forceUpdateAllSettings(self):
+ with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
+ property_names = ["value", "resolve", "validationState"]
+ for container in [self._global_container_stack] + list(self._global_container_stack.extruders.values()):
+ for setting_key in container.getAllKeys():
+ container.propertiesChanged.emit(setting_key, property_names)
+
+ @pyqtSlot(int, bool)
+ def setExtruderEnabled(self, position: int, enabled) -> None:
+ extruder = self.getExtruder(position)
+ extruder.setEnabled(enabled)
+ self.updateDefaultExtruder()
+ self.updateNumberExtrudersEnabled()
+ self.correctExtruderSettings()
+ # ensure that the quality profile is compatible with current combination, or choose a compatible one if available
+ self._updateQualityWithMaterial()
+ self.extruderChanged.emit()
+ # update material compatibility color
+ self.activeQualityGroupChanged.emit()
+ # update items in SettingExtruder
+ ExtruderManager.getInstance().extrudersChanged.emit(self._global_container_stack.getId())
+ # Make sure the front end reflects changes
+ self.forceUpdateAllSettings()
+
def _onMachineNameChanged(self):
self.globalContainerChanged.emit()
@@ -779,29 +924,24 @@ class MachineManager(QObject):
container = extruder.userChanges
container.setProperty(setting_name, property_name, property_value)
- @pyqtProperty("QVariantList", notify = rootMaterialChanged)
+ @pyqtProperty("QVariantList", notify = globalContainerChanged)
def currentExtruderPositions(self):
- return sorted(list(self._current_root_material_id.keys()))
+ if self._global_container_stack is None:
+ return []
+ return sorted(list(self._global_container_stack.extruders.keys()))
+
+ ## Update _current_root_material_id when the current root material was changed.
+ def _onRootMaterialChanged(self):
+ self._current_root_material_id = {}
+
+ if self._global_container_stack:
+ for position in self._global_container_stack.extruders:
+ self._current_root_material_id[position] = self._global_container_stack.extruders[position].material.getMetaDataEntry("base_file")
@pyqtProperty("QVariant", notify = rootMaterialChanged)
def currentRootMaterialId(self):
- # initial filling the current_root_material_id
- self._current_root_material_id = {}
- for position in self._global_container_stack.extruders:
- self._current_root_material_id[position] = self._global_container_stack.extruders[position].material.getMetaDataEntry("base_file")
return self._current_root_material_id
- @pyqtProperty("QVariant", notify = rootMaterialChanged)
- def currentRootMaterialName(self):
- # initial filling the current_root_material_name
- if self._global_container_stack:
- self._current_root_material_name = {}
- for position in self._global_container_stack.extruders:
- if position not in self._current_root_material_name:
- material = self._global_container_stack.extruders[position].material
- self._current_root_material_name[position] = material.getName()
- return self._current_root_material_name
-
## Return the variant names in the extruder stack(s).
## For the variant in the global stack, use activeVariantBuildplateName
@pyqtProperty("QVariant", notify = activeVariantChanged)
@@ -846,9 +986,9 @@ class MachineManager(QObject):
# Set quality and quality_changes for each ExtruderStack
for position, node in quality_group.nodes_for_extruders.items():
- self._global_container_stack.extruders[position].quality = node.getContainer()
+ self._global_container_stack.extruders[str(position)].quality = node.getContainer()
if empty_quality_changes:
- self._global_container_stack.extruders[position].qualityChanges = self._empty_quality_changes_container
+ self._global_container_stack.extruders[str(position)].qualityChanges = self._empty_quality_changes_container
self.activeQualityGroupChanged.emit()
self.activeQualityChangesGroupChanged.emit()
@@ -873,7 +1013,7 @@ class MachineManager(QObject):
quality_node = quality_group.nodes_for_extruders.get(position)
quality_changes_container = self._empty_quality_changes_container
- quality_container = self._empty_quality_changes_container
+ quality_container = self._empty_quality_container
if quality_changes_node:
quality_changes_container = quality_changes_node.getContainer()
if quality_node:
@@ -897,50 +1037,59 @@ class MachineManager(QObject):
def _setMaterial(self, position, container_node = None):
if container_node:
self._global_container_stack.extruders[position].material = container_node.getContainer()
+ root_material_id = container_node.metadata["base_file"]
else:
self._global_container_stack.extruders[position].material = self._empty_material_container
+ root_material_id = None
# The _current_root_material_id is used in the MaterialMenu to see which material is selected
- root_material_id = container_node.metadata["base_file"]
- root_material_name = container_node.getContainer().getName()
if root_material_id != self._current_root_material_id[position]:
self._current_root_material_id[position] = root_material_id
- self._current_root_material_name[position] = root_material_name
self.rootMaterialChanged.emit()
def activeMaterialsCompatible(self):
# check material - variant compatibility
if Util.parseBool(self._global_container_stack.getMetaDataEntry("has_materials", False)):
for position, extruder in self._global_container_stack.extruders.items():
+ if extruder.isEnabled and not extruder.material.getMetaDataEntry("compatible"):
+ return False
if not extruder.material.getMetaDataEntry("compatible"):
return False
return True
## Update current quality type and machine after setting material
- def _updateQualityWithMaterial(self):
- current_quality = None
+ def _updateQualityWithMaterial(self, *args):
+ Logger.log("i", "Updating quality/quality_changes due to material change")
+ current_quality_type = None
if self._current_quality_group:
- current_quality = self._current_quality_group.quality_type
- quality_manager = Application.getInstance()._quality_manager
- candidate_quality_groups = quality_manager.getQualityGroups(self._global_container_stack)
+ current_quality_type = self._current_quality_group.quality_type
+ candidate_quality_groups = self._quality_manager.getQualityGroups(self._global_container_stack)
available_quality_types = {qt for qt, g in candidate_quality_groups.items() if g.is_available}
+ Logger.log("d", "Current quality type = [%s]", current_quality_type)
if not self.activeMaterialsCompatible():
+ Logger.log("i", "Active materials are not compatible, setting all qualities to empty (Not Supported).")
self._setEmptyQuality()
return
if not available_quality_types:
+ Logger.log("i", "No available quality types found, setting all qualities to empty (Not Supported).")
self._setEmptyQuality()
return
- if current_quality in available_quality_types:
- self._setQualityGroup(candidate_quality_groups[current_quality], empty_quality_changes = False)
+ if current_quality_type in available_quality_types:
+ Logger.log("i", "Current available quality type [%s] is available, applying changes.", current_quality_type)
+ self._setQualityGroup(candidate_quality_groups[current_quality_type], empty_quality_changes = False)
return
+ # The current quality type is not available so we use the preferred quality type if it's available,
+ # otherwise use one of the available quality types.
quality_type = sorted(list(available_quality_types))[0]
preferred_quality_type = self._global_container_stack.getMetaDataEntry("preferred_quality_type")
if preferred_quality_type in available_quality_types:
quality_type = preferred_quality_type
+ Logger.log("i", "The current quality type [%s] is not available, switching to [%s] instead",
+ current_quality_type, quality_type)
self._setQualityGroup(candidate_quality_groups[quality_type], empty_quality_changes = True)
def _updateMaterialWithVariant(self, position: Optional[str]):
@@ -953,11 +1102,16 @@ class MachineManager(QObject):
extruder = self._global_container_stack.extruders[position]
current_material_base_name = extruder.material.getMetaDataEntry("base_file")
- current_variant_name = extruder.variant.getMetaDataEntry("name")
+ current_variant_name = None
+ if extruder.variant.getId() != self._empty_variant_container.getId():
+ current_variant_name = extruder.variant.getMetaDataEntry("name")
- material_manager = Application.getInstance()._material_manager
- material_diameter = self._global_container_stack.getProperty("material_diameter", "value")
- candidate_materials = material_manager.getAvailableMaterials(
+ from UM.Settings.Interfaces import PropertyEvaluationContext
+ from cura.Settings.CuraContainerStack import _ContainerIndexes
+ context = PropertyEvaluationContext(extruder)
+ context.context["evaluate_from_container_index"] = _ContainerIndexes.DefinitionChanges
+ material_diameter = extruder.getProperty("material_diameter", "value", context)
+ candidate_materials = self._material_manager.getAvailableMaterials(
self._global_container_stack.definition.getId(),
current_variant_name,
material_diameter)
@@ -971,6 +1125,92 @@ class MachineManager(QObject):
self._setMaterial(position, new_material)
continue
+ # The current material is not available, find the preferred one
+ material_node = self._material_manager.getDefaultMaterial(self._global_container_stack, current_variant_name)
+ if material_node is not None:
+ self._setMaterial(position, material_node)
+
+ ## Given a printer definition name, select the right machine instance. In case it doesn't exist, create a new
+ # instance with the same network key.
+ @pyqtSlot(str)
+ def switchPrinterType(self, machine_name):
+ # Don't switch if the user tries to change to the same type of printer
+ if self.activeMachineDefinitionName == machine_name:
+ return
+ # Get the definition id corresponding to this machine name
+ machine_definition_id = ContainerRegistry.getInstance().findDefinitionContainers(name = machine_name)[0].getId()
+ # Try to find a machine with the same network key
+ new_machine = self.getMachine(machine_definition_id, metadata_filter = {"um_network_key": self.activeMachineNetworkKey})
+ # If there is no machine, then create a new one and set it to the non-hidden instance
+ if not new_machine:
+ new_machine = CuraStackBuilder.createMachine(machine_definition_id + "_sync", machine_definition_id)
+ new_machine.addMetaDataEntry("um_network_key", self.activeMachineNetworkKey)
+ new_machine.addMetaDataEntry("connect_group_name", self.activeMachineNetworkGroupName)
+ new_machine.addMetaDataEntry("hidden", False)
+ else:
+ Logger.log("i", "Found a %s with the key %s. Let's use it!", machine_name, self.activeMachineNetworkKey)
+ new_machine.setMetaDataEntry("hidden", False)
+
+ # Set the current printer instance to hidden (the metadata entry must exist)
+ self._global_container_stack.setMetaDataEntry("hidden", True)
+
+ self.setActiveMachine(new_machine.getId())
+
+ @pyqtSlot(QObject)
+ def applyRemoteConfiguration(self, configuration: ConfigurationModel):
+ self.blurSettings.emit()
+ with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
+ self.switchPrinterType(configuration.printerType)
+ for extruder_configuration in configuration.extruderConfigurations:
+ position = str(extruder_configuration.position)
+ variant_container_node = self._variant_manager.getVariantNode(self._global_container_stack.definition.getId(), extruder_configuration.hotendID)
+ material_container_node = self._material_manager.getMaterialNodeByType(self._global_container_stack, extruder_configuration.hotendID,extruder_configuration.material.guid)
+ if variant_container_node:
+ self._setVariantNode(position, variant_container_node)
+ else:
+ self._global_container_stack.extruders[position].variant = self._empty_variant_container
+
+ if material_container_node:
+ self._setMaterial(position, material_container_node)
+ else:
+ self._global_container_stack.extruders[position].material = self._empty_material_container
+ self._updateMaterialWithVariant(position)
+
+ if configuration.buildplateConfiguration is not None:
+ global_variant_container_node = self._variant_manager.getBuildplateVariantNode(self._global_container_stack.definition.getId(), configuration.buildplateConfiguration)
+ if global_variant_container_node:
+ self._setGlobalVariant(global_variant_container_node)
+ else:
+ self._global_container_stack.variant = self._empty_variant_container
+ else:
+ self._global_container_stack.variant = self._empty_variant_container
+ self._updateQualityWithMaterial()
+
+ ## Find all container stacks that has the pair 'key = value' in its metadata and replaces the value with 'new_value'
+ def replaceContainersMetadata(self, key: str, value: str, new_value: str):
+ machines = ContainerRegistry.getInstance().findContainerStacks(type = "machine")
+ for machine in machines:
+ if machine.getMetaDataEntry(key) == value:
+ machine.setMetaDataEntry(key, new_value)
+
+ ## This method checks if the name of the group stored in the definition container is correct.
+ # After updating from 3.2 to 3.3 some group names may be temporary. If there is a mismatch in the name of the group
+ # then all the container stacks are updated, both the current and the hidden ones.
+ def checkCorrectGroupName(self, device_id: str, group_name: str):
+ if self._global_container_stack and device_id == self.activeMachineNetworkKey:
+ # Check if the connect_group_name is correct. If not, update all the containers connected to the same printer
+ if self.activeMachineNetworkGroupName != group_name:
+ metadata_filter = {"um_network_key": self.activeMachineNetworkKey}
+ hidden_containers = ContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
+ for container in hidden_containers:
+ container.setMetaDataEntry("connect_group_name", group_name)
+
+ ## This method checks if there is an instance connected to the given network_key
+ def existNetworkInstances(self, network_key: str) -> bool:
+ metadata_filter = {"um_network_key": network_key}
+ containers = ContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
+ return bool(containers)
+
@pyqtSlot("QVariant")
def setGlobalVariant(self, container_node):
self.blurSettings.emit()
@@ -979,6 +1219,16 @@ class MachineManager(QObject):
self._updateMaterialWithVariant(None) # Update all materials
self._updateQualityWithMaterial()
+ @pyqtSlot(str, str)
+ def setMaterialById(self, position, root_material_id):
+ machine_definition_id = self._global_container_stack.definition.id
+ position = str(position)
+ extruder_stack = self._global_container_stack.extruders[position]
+ variant_name = extruder_stack.variant.getName()
+ material_diameter = extruder_stack.approximateMaterialDiameter
+ material_node = self._material_manager.getMaterialNode(machine_definition_id, variant_name, material_diameter, root_material_id)
+ self.setMaterial(position, material_node)
+
@pyqtSlot(str, "QVariant")
def setMaterial(self, position, container_node):
position = str(position)
@@ -987,8 +1237,14 @@ class MachineManager(QObject):
self._setMaterial(position, container_node)
self._updateQualityWithMaterial()
+ @pyqtSlot(str, str)
+ def setVariantByName(self, position, variant_name):
+ machine_definition_id = self._global_container_stack.definition.id
+ variant_node = self._variant_manager.getVariantNode(machine_definition_id, variant_name)
+ self.setVariant(position, variant_node)
+
@pyqtSlot(str, "QVariant")
- def setVariantGroup(self, position, container_node):
+ def setVariant(self, position, container_node):
position = str(position)
self.blurSettings.emit()
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
@@ -996,29 +1252,43 @@ class MachineManager(QObject):
self._updateMaterialWithVariant(position)
self._updateQualityWithMaterial()
+ @pyqtSlot(str)
+ def setQualityGroupByQualityType(self, quality_type):
+ # Get all the quality groups for this global stack and filter out by quality_type
+ quality_group_dict = self._quality_manager.getQualityGroups(self._global_container_stack)
+ quality_group = quality_group_dict[quality_type]
+ self.setQualityGroup(quality_group)
+
@pyqtSlot(QObject)
- def setQualityGroup(self, quality_group):
+ def setQualityGroup(self, quality_group, no_dialog = False):
self.blurSettings.emit()
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
self._setQualityGroup(quality_group)
# See if we need to show the Discard or Keep changes screen
- if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1:
- Application.getInstance().discardOrKeepProfileChanges()
+ if not no_dialog and self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1:
+ self._application.discardOrKeepProfileChanges()
@pyqtProperty(QObject, fset = setQualityGroup, notify = activeQualityGroupChanged)
def activeQualityGroup(self):
return self._current_quality_group
@pyqtSlot(QObject)
- def setQualityChangesGroup(self, quality_changes_group):
+ def setQualityChangesGroup(self, quality_changes_group, no_dialog = False):
self.blurSettings.emit()
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
self._setQualityChangesGroup(quality_changes_group)
# See if we need to show the Discard or Keep changes screen
- if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1:
- Application.getInstance().discardOrKeepProfileChanges()
+ if not no_dialog and self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1:
+ self._application.discardOrKeepProfileChanges()
+
+ @pyqtSlot()
+ def resetToUseDefaultQuality(self):
+ with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
+ self._setQualityGroup(self._current_quality_group)
+ for stack in [self._global_container_stack] + list(self._global_container_stack.extruders.values()):
+ stack.userChanges.clear()
@pyqtProperty(QObject, fset = setQualityChangesGroup, notify = activeQualityChangesGroupChanged)
def activeQualityChangesGroup(self):
@@ -1032,3 +1302,8 @@ class MachineManager(QObject):
elif self._current_quality_group:
name = self._current_quality_group.name
return name
+
+ def _updateUponMaterialMetadataChange(self):
+ with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
+ self._updateMaterialWithVariant(None)
+ self._updateQualityWithMaterial()
diff --git a/cura/Settings/PerObjectContainerStack.py b/cura/Settings/PerObjectContainerStack.py
index 6c54ed46d5..33111cbed7 100644
--- a/cura/Settings/PerObjectContainerStack.py
+++ b/cura/Settings/PerObjectContainerStack.py
@@ -3,13 +3,14 @@ from typing import Any, Optional
from UM.Application import Application
from UM.Decorators import override
from UM.Settings.Interfaces import PropertyEvaluationContext
-from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.SettingInstance import InstanceState
+from .CuraContainerStack import CuraContainerStack
-class PerObjectContainerStack(ContainerStack):
- @override(ContainerStack)
+class PerObjectContainerStack(CuraContainerStack):
+
+ @override(CuraContainerStack)
def getProperty(self, key: str, property_name: str, context: Optional[PropertyEvaluationContext] = None) -> Any:
if context is None:
context = PropertyEvaluationContext()
@@ -51,8 +52,8 @@ class PerObjectContainerStack(ContainerStack):
context.popContainer()
return result
- @override(ContainerStack)
- def setNextStack(self, stack: ContainerStack):
+ @override(CuraContainerStack)
+ def setNextStack(self, stack: CuraContainerStack):
super().setNextStack(stack)
# trigger signal to re-evaluate all default settings
diff --git a/cura/Settings/SettingInheritanceManager.py b/cura/Settings/SettingInheritanceManager.py
index 0d4cd02cdb..e317b20f68 100644
--- a/cura/Settings/SettingInheritanceManager.py
+++ b/cura/Settings/SettingInheritanceManager.py
@@ -1,7 +1,7 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal
+from PyQt5.QtCore import QObject, QTimer, pyqtProperty, pyqtSignal
from UM.FlameProfiler import pyqtSlot
from UM.Application import Application
from UM.Logger import Logger
@@ -30,6 +30,11 @@ class SettingInheritanceManager(QObject):
ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderChanged)
self._onActiveExtruderChanged()
+ self._update_timer = QTimer()
+ self._update_timer.setInterval(500)
+ self._update_timer.setSingleShot(True)
+ self._update_timer.timeout.connect(self._update)
+
settingsWithIntheritanceChanged = pyqtSignal()
## Get the keys of all children settings with an override.
@@ -226,9 +231,7 @@ class SettingInheritanceManager(QObject):
self._onActiveExtruderChanged()
def _onContainersChanged(self, container):
- # TODO: Multiple container changes in sequence now cause quite a few recalculations.
- # This isn't that big of an issue, but it could be in the future.
- self._update()
+ self._update_timer.start()
@staticmethod
def createSettingInheritanceManager(engine=None, script_engine=None):
diff --git a/cura/Settings/SettingOverrideDecorator.py b/cura/Settings/SettingOverrideDecorator.py
index 6e98f014dc..e853a3a979 100644
--- a/cura/Settings/SettingOverrideDecorator.py
+++ b/cura/Settings/SettingOverrideDecorator.py
@@ -3,14 +3,12 @@
import copy
-from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
from UM.Signal import Signal, signalemitter
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Logger import Logger
-from UM.Settings.Validator import ValidatorState
-from PyQt5.QtCore import QTimer
+
from UM.Application import Application
from cura.Settings.PerObjectContainerStack import PerObjectContainerStack
@@ -34,16 +32,14 @@ class SettingOverrideDecorator(SceneNodeDecorator):
def __init__(self):
super().__init__()
- self._stack = PerObjectContainerStack(stack_id = "per_object_stack_" + str(id(self)))
+ self._stack = PerObjectContainerStack(container_id = "per_object_stack_" + str(id(self)))
self._stack.setDirty(False) # This stack does not need to be saved.
- self._stack.addContainer(InstanceContainer(container_id = "SettingOverrideInstanceContainer"))
+ user_container = InstanceContainer(container_id = "SettingOverrideInstanceContainer")
+ user_container.addMetaDataEntry("type", "user")
+ self._stack.userChanges = user_container
self._extruder_stack = ExtruderManager.getInstance().getExtruderStack(0).getId()
self._is_non_printing_mesh = False
- self._error_check_timer = QTimer()
- self._error_check_timer.setInterval(250)
- self._error_check_timer.setSingleShot(True)
- self._error_check_timer.timeout.connect(self._checkStackForErrors)
self._stack.propertyChanged.connect(self._onSettingChanged)
@@ -67,7 +63,7 @@ class SettingOverrideDecorator(SceneNodeDecorator):
# use value from the stack because there can be a delay in signal triggering and "_is_non_printing_mesh"
# has not been updated yet.
- deep_copy._is_non_printing_mesh = any(bool(self._stack.getProperty(setting, "value")) for setting in self._non_printing_mesh_settings)
+ deep_copy._is_non_printing_mesh = self.evaluateIsNonPrintingMesh()
return deep_copy
@@ -95,25 +91,16 @@ class SettingOverrideDecorator(SceneNodeDecorator):
def isNonPrintingMesh(self):
return self._is_non_printing_mesh
- def _onSettingChanged(self, instance, property_name): # Reminder: 'property' is a built-in function
- # Trigger slice/need slicing if the value has changed.
- if property_name == "value":
- self._is_non_printing_mesh = any(bool(self._stack.getProperty(setting, "value")) for setting in self._non_printing_mesh_settings)
- if not self._is_non_printing_mesh:
- # self._error_check_timer.start()
- self._checkStackForErrors()
- Application.getInstance().getBackend().needsSlicing()
- Application.getInstance().getBackend().tickle()
+ def evaluateIsNonPrintingMesh(self):
+ return any(bool(self._stack.getProperty(setting, "value")) for setting in self._non_printing_mesh_settings)
- def _checkStackForErrors(self):
- hasErrors = False;
- for key in self._stack.getAllKeys():
- validation_state = self._stack.getProperty(key, "validationState")
- if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError):
- Logger.log("w", "Setting Per Object %s is not valid.", key)
- hasErrors = True
- break
- Application.getInstance().getObjectsModel().setStacksHaveErrors(hasErrors)
+ def _onSettingChanged(self, instance, property_name): # Reminder: 'property' is a built-in function
+ if property_name == "value":
+ # Trigger slice/need slicing if the value has changed.
+ self._is_non_printing_mesh = self.evaluateIsNonPrintingMesh()
+
+ Application.getInstance().getBackend().needsSlicing()
+ Application.getInstance().getBackend().tickle()
## Makes sure that the stack upon which the container stack is placed is
# kept up to date.
diff --git a/cura/Settings/SimpleModeSettingsManager.py b/cura/Settings/SimpleModeSettingsManager.py
index 867a21702c..a337d8b04e 100644
--- a/cura/Settings/SimpleModeSettingsManager.py
+++ b/cura/Settings/SimpleModeSettingsManager.py
@@ -16,7 +16,8 @@ class SimpleModeSettingsManager(QObject):
self._is_profile_user_created = False # True when profile was custom created by user
self._machine_manager.activeStackValueChanged.connect(self._updateIsProfileCustomized)
- self._machine_manager.activeQualityChanged.connect(self._updateIsProfileUserCreated)
+ self._machine_manager.activeQualityGroupChanged.connect(self._updateIsProfileUserCreated)
+ self._machine_manager.activeQualityChangesGroupChanged.connect(self._updateIsProfileUserCreated)
# update on create as the activeQualityChanged signal is emitted before this manager is created when Cura starts
self._updateIsProfileCustomized()
diff --git a/cura/Snapshot.py b/cura/Snapshot.py
index 2a2a49d6cf..1f2a24aecd 100644
--- a/cura/Snapshot.py
+++ b/cura/Snapshot.py
@@ -66,7 +66,7 @@ class Snapshot:
size = max(bbox.width, bbox.height, bbox.depth * 0.5)
# Looking from this direction (x, y, z) in OGL coordinates
- looking_from_offset = Vector(1, 1, 2)
+ looking_from_offset = Vector(-1, 1, 2)
if size > 0:
# determine the watch distance depending on the size
looking_from_offset = looking_from_offset * size * 1.3
diff --git a/plugins/3MFReader/ThreeMFReader.py b/plugins/3MFReader/ThreeMFReader.py
index ec590a0212..6c2fb9a59d 100755
--- a/plugins/3MFReader/ThreeMFReader.py
+++ b/plugins/3MFReader/ThreeMFReader.py
@@ -16,7 +16,6 @@ from UM.Mesh.MeshBuilder import MeshBuilder
from UM.Mesh.MeshReader import MeshReader
from UM.Scene.GroupDecorator import GroupDecorator
-from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
from cura.Settings.ExtruderManager import ExtruderManager
from cura.Scene.CuraSceneNode import CuraSceneNode
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
@@ -81,7 +80,7 @@ class ThreeMFReader(MeshReader):
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
- um_node = CuraSceneNode()
+ um_node = CuraSceneNode() # This adds a SettingOverrideDecorator
um_node.addDecorator(BuildPlateDecorator(active_build_plate))
um_node.setName(node_name)
transformation = self._createMatrixFromTransformationString(savitar_node.getTransformation())
@@ -110,8 +109,6 @@ class ThreeMFReader(MeshReader):
# Add the setting override decorator, so we can add settings to this node.
if settings:
- um_node.addDecorator(SettingOverrideDecorator())
-
global_container_stack = Application.getInstance().getGlobalContainerStack()
# Ensure the correct next container for the SettingOverride decorator is set.
@@ -122,7 +119,7 @@ class ThreeMFReader(MeshReader):
um_node.callDecoration("setActiveExtruder", default_stack.getId())
# Get the definition & set it
- definition_id = getMachineDefinitionIDForQualitySearch(global_container_stack)
+ definition_id = getMachineDefinitionIDForQualitySearch(global_container_stack.definition)
um_node.callDecoration("getStack").getTop().setDefinition(definition_id)
setting_container = um_node.callDecoration("getStack").getTop()
@@ -140,7 +137,7 @@ class ThreeMFReader(MeshReader):
continue
setting_container.setProperty(key, "value", setting_value)
- if len(um_node.getChildren()) > 0:
+ if len(um_node.getChildren()) > 0 and um_node.getMeshData() is None:
group_decorator = GroupDecorator()
um_node.addDecorator(group_decorator)
um_node.setSelectable(True)
diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py
index cbe882f253..633142187c 100755
--- a/plugins/3MFReader/ThreeMFWorkspaceReader.py
+++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py
@@ -1,11 +1,19 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
+from configparser import ConfigParser
+import zipfile
+import os
+import threading
+
+import xml.etree.ElementTree as ET
+
from UM.Workspace.WorkspaceReader import WorkspaceReader
from UM.Application import Application
from UM.Logger import Logger
from UM.i18n import i18nCatalog
+from UM.Signal import postponeSignals, CompressTechnique
from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.DefinitionContainer import DefinitionContainer
from UM.Settings.InstanceContainer import InstanceContainer
@@ -13,24 +21,14 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.MimeTypeDatabase import MimeTypeDatabase
from UM.Job import Job
from UM.Preferences import Preferences
-from UM.Util import parseBool
-from .WorkspaceDialog import WorkspaceDialog
-
-import xml.etree.ElementTree as ET
from cura.Settings.CuraStackBuilder import CuraStackBuilder
-from cura.Settings.ExtruderManager import ExtruderManager
from cura.Settings.ExtruderStack import ExtruderStack
from cura.Settings.GlobalStack import GlobalStack
from cura.Settings.CuraContainerStack import _ContainerIndexes
from cura.CuraApplication import CuraApplication
-from configparser import ConfigParser
-import zipfile
-import io
-import configparser
-import os
-import threading
+from .WorkspaceDialog import WorkspaceDialog
i18n_catalog = i18nCatalog("cura")
@@ -66,6 +64,49 @@ def call_on_qt_thread(func):
return _call_on_qt_thread_wrapper
+class ContainerInfo:
+ def __init__(self, file_name: str, serialized: str, parser: ConfigParser):
+ self.file_name = file_name
+ self.serialized = serialized
+ self.parser = parser
+ self.container = None
+ self.definition_id = None
+
+
+class QualityChangesInfo:
+ def __init__(self):
+ self.name = None
+ self.global_info = None
+ self.extruder_info_dict = {}
+
+
+class MachineInfo:
+ def __init__(self):
+ self.container_id = None
+ self.name = None
+ self.definition_id = None
+ self.quality_type = None
+ self.custom_quality_name = None
+ self.quality_changes_info = None
+ self.variant_info = None
+
+ self.definition_changes_info = None
+ self.user_changes_info = None
+
+ self.extruder_info_dict = {}
+
+
+class ExtruderInfo:
+ def __init__(self):
+ self.position = None
+ self.enabled = True
+ self.variant_info = None
+ self.root_material_id = None
+
+ self.definition_changes_info = None
+ self.user_changes_info = None
+
+
## Base implementation for reading 3MF workspace files.
class ThreeMFWorkspaceReader(WorkspaceReader):
def __init__(self):
@@ -96,6 +137,18 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# In Cura 2.5 and 2.6, the empty profiles used to have those long names
self._old_empty_profile_id_dict = {"empty_%s" % k: "empty" for k in ["material", "variant"]}
+ self._is_same_machine_type = False
+ self._old_new_materials = {}
+ self._materials_to_select = {}
+ self._machine_info = None
+
+ def _clearState(self):
+ self._is_same_machine_type = False
+ self._id_mapping = {}
+ self._old_new_materials = {}
+ self._materials_to_select = {}
+ self._machine_info = None
+
## Get a unique name based on the old_id. This is different from directly calling the registry in that it caches results.
# This has nothing to do with speed, but with getting consistent new naming for instances & objects.
def getNewId(self, old_id):
@@ -147,6 +200,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# \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._clearState()
+
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:
pass
@@ -154,6 +209,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
Logger.log("w", "Could not find reader that was able to read the scene data for 3MF workspace")
return WorkspaceReader.PreReadResult.failed
+ self._machine_info = MachineInfo()
machine_type = ""
variant_type_name = i18n_catalog.i18nc("@label", "Nozzle")
@@ -161,11 +217,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
archive = zipfile.ZipFile(file_name, "r")
cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")]
- # 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.
- instance_container_list = []
-
resolve_strategy_keys = ["machine", "material", "quality_changes"]
self._resolve_strategies = {k: None for k in resolve_strategy_keys}
containers_found_dict = {k: False for k in resolve_strategy_keys}
@@ -173,23 +224,23 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
#
# Read definition containers
#
+ machine_definition_id = None
machine_definition_container_count = 0
extruder_definition_container_count = 0
definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
- for each_definition_container_file in definition_container_files:
- container_id = self._stripFileToId(each_definition_container_file)
+ for definition_container_file in definition_container_files:
+ container_id = self._stripFileToId(definition_container_file)
definitions = self._container_registry.findDefinitionContainersMetadata(id = container_id)
+ serialized = archive.open(definition_container_file).read().decode("utf-8")
if not definitions:
- definition_container = DefinitionContainer(container_id)
- definition_container.deserialize(archive.open(each_definition_container_file).read().decode("utf-8"), file_name = each_definition_container_file)
- definition_container = definition_container.getMetaData()
-
+ definition_container = DefinitionContainer.deserializeMetadata(serialized, container_id)[0]
else:
definition_container = definitions[0]
definition_container_type = definition_container.get("type")
if definition_container_type == "machine":
+ machine_definition_id = container_id
machine_type = definition_container["name"]
variant_type_name = definition_container.get("variants_name", variant_type_name)
@@ -198,22 +249,29 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
extruder_definition_container_count += 1
else:
Logger.log("w", "Unknown definition container type %s for %s",
- definition_container_type, each_definition_container_file)
+ definition_container_type, definition_container_file)
Job.yieldThread()
if machine_definition_container_count != 1:
- return WorkspaceReader.PreReadResult.failed #Not a workspace file but ordinary 3MF.
+ return WorkspaceReader.PreReadResult.failed # Not a workspace file but ordinary 3MF.
material_labels = []
material_conflict = False
xml_material_profile = self._getXmlProfileClass()
+ reverse_material_id_dict = {}
if self._material_container_suffix is None:
self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(xml_material_profile).preferredSuffix
if xml_material_profile:
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)
- material_labels.append(self._getMaterialLabelFromSerialized(archive.open(material_container_file).read().decode("utf-8")))
+
+ serialized = archive.open(material_container_file).read().decode("utf-8")
+ metadata_list = xml_material_profile.deserializeMetadata(serialized, container_id)
+ reverse_map = {metadata["id"]: container_id for metadata in metadata_list}
+ reverse_material_id_dict.update(reverse_map)
+
+ material_labels.append(self._getMaterialLabelFromSerialized(serialized))
if self._container_registry.findContainersMetadata(id = container_id): #This material already exists.
containers_found_dict["material"] = True
if not self._container_registry.isReadOnly(container_id): # Only non readonly materials can be in conflict
@@ -223,49 +281,60 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# Check if any quality_changes instance container is in conflict.
instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
quality_name = ""
- quality_type = ""
+ custom_quality_name = ""
num_settings_overriden_by_quality_changes = 0 # How many settings are changed by the quality changes
- num_settings_overriden_by_definition_changes = 0 # How many settings are changed by the definition changes
num_user_settings = 0
quality_changes_conflict = False
- definition_changes_conflict = False
- for each_instance_container_file in instance_container_files:
- container_id = self._stripFileToId(each_instance_container_file)
- instance_container = InstanceContainer(container_id)
+ self._machine_info.quality_changes_info = QualityChangesInfo()
- # Deserialize InstanceContainer by converting read data from bytes to string
- instance_container.deserialize(archive.open(each_instance_container_file).read().decode("utf-8"),
- file_name = each_instance_container_file)
- instance_container_list.append(instance_container)
+ quality_changes_info_list = []
+ instance_container_info_dict = {} # id -> parser
+ for instance_container_file_name in instance_container_files:
+ container_id = self._stripFileToId(instance_container_file_name)
- container_type = instance_container.getMetaDataEntry("type")
+ serialized = archive.open(instance_container_file_name).read().decode("utf-8")
+
+ # Qualities and variants don't have upgrades, so don't upgrade them
+ parser = ConfigParser(interpolation = None)
+ parser.read_string(serialized)
+ container_type = parser["metadata"]["type"]
+ if container_type not in ("quality", "variant"):
+ serialized = InstanceContainer._updateSerialized(serialized, instance_container_file_name)
+
+ parser = ConfigParser(interpolation = None)
+ parser.read_string(serialized)
+ container_info = ContainerInfo(instance_container_file_name, serialized, parser)
+ instance_container_info_dict[container_id] = container_info
+
+ container_type = parser["metadata"]["type"]
if container_type == "quality_changes":
- quality_name = instance_container.getName()
- num_settings_overriden_by_quality_changes += len(instance_container._instances)
+ quality_changes_info_list.append(container_info)
+
+ if not parser.has_option("metadata", "position"):
+ self._machine_info.quality_changes_info.name = parser["general"]["name"]
+ self._machine_info.quality_changes_info.global_info = container_info
+ else:
+ position = parser["metadata"]["position"]
+ self._machine_info.quality_changes_info.extruder_info_dict[position] = container_info
+
+ custom_quality_name = parser["general"]["name"]
+ values = parser["values"] if parser.has_section("values") else dict()
+ num_settings_overriden_by_quality_changes += len(values)
# Check if quality changes already exists.
quality_changes = self._container_registry.findInstanceContainers(id = container_id)
if quality_changes:
containers_found_dict["quality_changes"] = True
# Check if there really is a conflict by comparing the values
+ instance_container = InstanceContainer(container_id)
+ instance_container.deserialize(serialized, file_name = instance_container_file_name)
if quality_changes[0] != instance_container:
quality_changes_conflict = True
- elif container_type == "definition_changes":
- definition_name = instance_container.getName()
- num_settings_overriden_by_definition_changes += len(instance_container._instances)
- # Check if definition changes already exists.
- definition_changes = self._container_registry.findInstanceContainers(id = container_id)
- # Check if there is any difference the loaded settings from the project file and the settings in Cura.
- if definition_changes:
- containers_found_dict["definition_changes"] = True
- # Check if there really is a conflict by comparing the values
- if definition_changes[0] != instance_container:
- definition_changes_conflict = True
elif container_type == "quality":
if not quality_name:
- quality_name = instance_container.getName()
+ quality_name = parser["general"]["name"]
elif container_type == "user":
- num_user_settings += len(instance_container._instances)
+ num_user_settings += len(parser["values"])
elif container_type in self._ignored_instance_container_types:
# Ignore certain instance container types
Logger.log("w", "Ignoring instance container [%s] with type [%s]", container_id, container_type)
@@ -273,6 +342,9 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
Job.yieldThread()
+ if self._machine_info.quality_changes_info.global_info is None:
+ self._machine_info.quality_changes_info = None
+
# Load ContainerStack files and ExtruderStack files
global_stack_file, extruder_stack_files = self._determineGlobalAndExtruderStackFiles(
file_name, cura_file_names)
@@ -281,12 +353,15 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# - the global stack exists but some/all of the extruder stacks DON'T exist
# - the global stack DOESN'T exist but some/all of the extruder stacks exist
# To simplify this, only check if the global stack exists or not
- container_id = self._stripFileToId(global_stack_file)
+ global_stack_id = self._stripFileToId(global_stack_file)
serialized = archive.open(global_stack_file).read().decode("utf-8")
machine_name = self._getMachineNameFromSerializedStack(serialized)
- stacks = self._container_registry.findContainerStacks(id = container_id)
+ stacks = self._container_registry.findContainerStacks(name = machine_name, type = "machine")
+ self._is_same_machine_type = True
+ existing_global_stack = None
if stacks:
global_stack = stacks[0]
+ existing_global_stack = global_stack
containers_found_dict["machine"] = True
# Check if there are any changes at all in any of the container stacks.
id_list = self._getContainerIdListFromSerialized(serialized)
@@ -296,30 +371,81 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
if global_stack.getContainer(index).getId() != container_id:
machine_conflict = True
break
+ self._is_same_machine_type = global_stack.definition.getId() == machine_definition_id
+
+ # Get quality type
+ parser = ConfigParser(interpolation = None)
+ parser.read_string(serialized)
+ quality_container_id = parser["containers"][str(_ContainerIndexes.Quality)]
+ quality_type = instance_container_info_dict[quality_container_id].parser["metadata"]["quality_type"]
+
+ # Get machine info
+ serialized = archive.open(global_stack_file).read().decode("utf-8")
+ serialized = GlobalStack._updateSerialized(serialized, global_stack_file)
+ parser = ConfigParser(interpolation = None)
+ parser.read_string(serialized)
+ definition_changes_id = parser["containers"][str(_ContainerIndexes.DefinitionChanges)]
+ if definition_changes_id not in ("empty", "empty_definition_changes"):
+ self._machine_info.definition_changes_info = instance_container_info_dict[definition_changes_id]
+ user_changes_id = parser["containers"][str(_ContainerIndexes.UserChanges)]
+ if user_changes_id not in ("empty", "empty_user_changes"):
+ self._machine_info.user_changes_info = instance_container_info_dict[user_changes_id]
+
+ # Also check variant and material in case it doesn't have extruder stacks
+ if not extruder_stack_files:
+ position = "0"
+
+ extruder_info = ExtruderInfo()
+ extruder_info.position = position
+ variant_id = parser["containers"][str(_ContainerIndexes.Variant)]
+ material_id = parser["containers"][str(_ContainerIndexes.Material)]
+ if variant_id not in ("empty", "empty_variant"):
+ extruder_info.variant_info = instance_container_info_dict[variant_id]
+ if material_id not in ("empty", "empty_material"):
+ root_material_id = reverse_material_id_dict[material_id]
+ extruder_info.root_material_id = root_material_id
+ self._machine_info.extruder_info_dict[position] = extruder_info
+ else:
+ variant_id = parser["containers"][str(_ContainerIndexes.Variant)]
+ if variant_id not in ("empty", "empty_variant"):
+ self._machine_info.variant_info = instance_container_info_dict[variant_id]
+
Job.yieldThread()
# if the global stack is found, we check if there are conflicts in the extruder stacks
- if containers_found_dict["machine"] and not machine_conflict:
- for extruder_stack_file in extruder_stack_files:
- serialized = archive.open(extruder_stack_file).read().decode("utf-8")
- parser = configparser.ConfigParser(interpolation = None)
- parser.read_string(serialized)
+ for extruder_stack_file in extruder_stack_files:
+ serialized = archive.open(extruder_stack_file).read().decode("utf-8")
+ serialized = ExtruderStack._updateSerialized(serialized, extruder_stack_file)
+ parser = ConfigParser(interpolation = None)
+ parser.read_string(serialized)
- # The check should be done for the extruder stack that's associated with the existing global stack,
- # and those extruder stacks may have different IDs.
- # So we check according to the positions
+ # The check should be done for the extruder stack that's associated with the existing global stack,
+ # and those extruder stacks may have different IDs.
+ # So we check according to the positions
+ position = parser["metadata"]["position"]
+ variant_id = parser["containers"][str(_ContainerIndexes.Variant)]
+ material_id = parser["containers"][str(_ContainerIndexes.Material)]
- position = str(parser["metadata"]["position"])
+ extruder_info = ExtruderInfo()
+ extruder_info.position = position
+ if parser.has_option("metadata", "enabled"):
+ extruder_info.enabled = parser["metadata"]["enabled"]
+ if variant_id not in ("empty", "empty_variant"):
+ extruder_info.variant_info = instance_container_info_dict[variant_id]
+ if material_id not in ("empty", "empty_material"):
+ root_material_id = reverse_material_id_dict[material_id]
+ extruder_info.root_material_id = root_material_id
+ definition_changes_id = parser["containers"][str(_ContainerIndexes.DefinitionChanges)]
+ if definition_changes_id not in ("empty", "empty_definition_changes"):
+ extruder_info.definition_changes_info = instance_container_info_dict[definition_changes_id]
+ user_changes_id = parser["containers"][str(_ContainerIndexes.UserChanges)]
+ if user_changes_id not in ("empty", "empty_user_changes"):
+ extruder_info.user_changes_info = instance_container_info_dict[user_changes_id]
+ self._machine_info.extruder_info_dict[position] = extruder_info
+
+ if not machine_conflict and containers_found_dict["machine"]:
if position not in global_stack.extruders:
- # The extruder position defined in the project doesn't exist in this global stack.
- # We can say that it is a machine conflict, but it is very hard to override the machine in this
- # case because we need to override the existing extruders and add the non-existing extruders.
- #
- # HACK:
- # To make this simple, we simply say that there is no machine conflict and create a new machine
- # by default.
- machine_conflict = False
- break
+ continue
existing_extruder_stack = global_stack.extruders[position]
# check if there are any changes at all in any of the container stacks.
@@ -360,10 +486,28 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
extruders = num_extruders * [""]
+ quality_name = custom_quality_name if custom_quality_name else quality_name
+
+ self._machine_info.container_id = global_stack_id
+ self._machine_info.name = machine_name
+ self._machine_info.definition_id = machine_definition_id
+ self._machine_info.quality_type = quality_type
+ self._machine_info.custom_quality_name = quality_name
+
+ if machine_conflict and not self._is_same_machine_type:
+ machine_conflict = False
+
+ is_printer_group = False
+ if machine_conflict:
+ group_name = existing_global_stack.getMetaDataEntry("connect_group_name")
+ if group_name is not None:
+ is_printer_group = True
+ machine_name = group_name
+
# Show the dialog, informing the user what is about to happen.
self._dialog.setMachineConflict(machine_conflict)
+ self._dialog.setIsPrinterGroup(is_printer_group)
self._dialog.setQualityChangesConflict(quality_changes_conflict)
- self._dialog.setDefinitionChangesConflict(definition_changes_conflict)
self._dialog.setMaterialConflict(material_conflict)
self._dialog.setHasVisibleSettingsField(has_visible_settings_string)
self._dialog.setNumVisibleSettings(num_visible_settings)
@@ -406,7 +550,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
## Overrides an ExtruderStack in the given GlobalStack and returns the new ExtruderStack.
def _overrideExtruderStack(self, global_stack, extruder_file_content, extruder_stack_file):
# Get extruder position first
- extruder_config = configparser.ConfigParser(interpolation = None)
+ extruder_config = ConfigParser(interpolation = None)
extruder_config.read_string(extruder_file_content)
if not extruder_config.has_option("metadata", "position"):
msg = "Could not find 'metadata/position' in extruder stack file"
@@ -434,6 +578,27 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# \param file_name
@call_on_qt_thread
def read(self, file_name):
+ container_registry = ContainerRegistry.getInstance()
+ signals = [container_registry.containerAdded,
+ container_registry.containerRemoved,
+ container_registry.containerMetaDataChanged]
+ #
+ # We now have different managers updating their lookup tables upon container changes. It is critical to make
+ # sure that the managers have a complete set of data when they update.
+ #
+ # In project loading, lots of the container-related signals are loosely emitted, which can create timing gaps
+ # for incomplete data update or other kinds of issues to happen.
+ #
+ # To avoid this, we postpone all signals so they don't get emitted immediately. But, please also be aware that,
+ # because of this, do not expect to have the latest data in the lookup tables in project loading.
+ #
+ with postponeSignals(*signals, compress = CompressTechnique.NoCompression):
+ return self._read(file_name)
+
+ def _read(self, file_name):
+ application = CuraApplication.getInstance()
+ material_manager = application.getMaterialManager()
+
archive = zipfile.ZipFile(file_name, "r")
cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")]
@@ -452,7 +617,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
Logger.log("w", "Workspace did not contain visible settings. Leaving visibility unchanged")
else:
global_preferences.setValue("general/visible_settings", visible_settings)
- global_preferences.setValue("general/preset_setting_visibility_choice", "Custom")
+ global_preferences.setValue("cura/active_setting_visibility_preset", "custom")
categories_expanded = temp_preferences.getValue("cura/categories_expanded")
if categories_expanded is None:
@@ -460,52 +625,24 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
else:
global_preferences.setValue("cura/categories_expanded", categories_expanded)
- Application.getInstance().expandedCategoriesChanged.emit() # Notify the GUI of the change
+ application.expandedCategoriesChanged.emit() # Notify the GUI of the change
- self._id_mapping = {}
+ # If a machine with the same name is of a different type, always create a new one.
+ if not self._is_same_machine_type or self._resolve_strategies["machine"] != "override":
+ # We need to create a new machine
+ machine_name = self._container_registry.uniqueName(self._machine_info.name)
- # We don't add containers right away, but wait right until right before the stack serialization.
- # We do this so that if something goes wrong, it's easier to clean up.
- containers_to_add = []
+ global_stack = CuraStackBuilder.createMachine(machine_name, self._machine_info.definition_id)
+ extruder_stack_dict = global_stack.extruders
- global_stack_file, extruder_stack_files = self._determineGlobalAndExtruderStackFiles(file_name, cura_file_names)
+ self._container_registry.addContainer(global_stack)
+ else:
+ # Find the machine
+ global_stack = self._container_registry.findContainerStacks(name = self._machine_info.name, type = "machine")[0]
+ extruder_stacks = self._container_registry.findContainerStacks(machine = global_stack.getId(),
+ type = "extruder_train")
+ extruder_stack_dict = {stack.getMetaDataEntry("position"): stack for stack in extruder_stacks}
- global_stack = None
- extruder_stacks = []
- extruder_stacks_added = []
- container_stacks_added = []
- machine_extruder_count = None
-
- containers_added = []
-
- global_stack_id_original = self._stripFileToId(global_stack_file)
- global_stack_id_new = global_stack_id_original
- global_stack_name_original = self._getMachineNameFromSerializedStack(archive.open(global_stack_file).read().decode("utf-8"))
- global_stack_name_new = global_stack_name_original
- global_stack_need_rename = False
-
- extruder_stack_id_map = {} # new and old ExtruderStack IDs map
- if self._resolve_strategies["machine"] == "new":
- # We need a new id if the id already exists
- if self._container_registry.findContainerStacksMetadata(id = global_stack_id_original):
- global_stack_id_new = self.getNewId(global_stack_id_original)
- global_stack_need_rename = True
-
- if self._container_registry.findContainerStacksMetadata(name = global_stack_id_original):
- global_stack_name_new = self._container_registry.uniqueName(global_stack_name_original)
-
- for each_extruder_stack_file in extruder_stack_files:
- old_container_id = self._stripFileToId(each_extruder_stack_file)
- new_container_id = old_container_id
- if self._container_registry.findContainerStacksMetadata(id = old_container_id):
- # get a new name for this extruder
- new_container_id = self.getNewId(old_container_id)
-
- extruder_stack_id_map[old_container_id] = new_container_id
-
- # 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.
Logger.log("d", "Workspace loading is checking definitions...")
# Get all the definition files & check if they exist. If not, add them.
definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
@@ -520,7 +657,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
Job.yieldThread()
Logger.log("d", "Workspace loading is checking materials...")
- material_containers = []
# Get all the material files and check if they exist. If not, add them.
xml_material_profile = self._getXmlProfileClass()
if self._material_container_suffix is None:
@@ -528,559 +664,49 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
if xml_material_profile:
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:
+ to_deserialize_material = False
container_id = self._stripFileToId(material_container_file)
+ need_new_name = False
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"),
- file_name = material_container_file)
- containers_to_add.append(material_container)
+ # No material found, deserialize this material later and add it
+ to_deserialize_material = True
else:
material_container = materials[0]
- if not self._container_registry.isReadOnly(container_id): # Only create new materials if they are not read only.
+ old_material_root_id = material_container.getMetaDataEntry("base_file")
+ if not self._container_registry.isReadOnly(old_material_root_id): # Only create new materials if they are not read only.
+ to_deserialize_material = True
+
if self._resolve_strategies["material"] == "override":
- material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"),
- file_name = material_container_file)
+ # Remove the old materials and then deserialize the one from the project
+ root_material_id = material_container.getMetaDataEntry("base_file")
+ material_manager.removeMaterialByRootId(root_material_id)
elif self._resolve_strategies["material"] == "new":
# Note that we *must* deserialize it with a new ID, as multiple containers will be
# auto created & added.
- material_container = xml_material_profile(self.getNewId(container_id))
- material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"),
- file_name = material_container_file)
- containers_to_add.append(material_container)
+ container_id = self.getNewId(container_id)
+ self._old_new_materials[old_material_root_id] = container_id
+ need_new_name = True
- material_containers.append(material_container)
+ if to_deserialize_material:
+ material_container = xml_material_profile(container_id)
+ material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"),
+ file_name = container_id + "." + self._material_container_suffix)
+ if need_new_name:
+ new_name = ContainerRegistry.getInstance().uniqueName(material_container.getName())
+ material_container.setName(new_name)
+ material_container.setDirty(True)
+ self._container_registry.addContainer(material_container)
Job.yieldThread()
- Logger.log("d", "Workspace loading is checking instance containers...")
- # 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_and_definition_changes_instance_containers = []
- quality_changes_instance_containers = []
- for instance_container_file in instance_container_files:
- container_id = self._stripFileToId(instance_container_file)
- serialized = archive.open(instance_container_file).read().decode("utf-8")
+ # Handle quality changes if any
+ self._processQualityChanges(global_stack)
- # HACK! we ignore "quality" and "variant" instance containers!
- parser = configparser.ConfigParser(interpolation = None)
- parser.read_string(serialized)
- if not parser.has_option("metadata", "type"):
- Logger.log("w", "Cannot find metadata/type in %s, ignoring it", instance_container_file)
- continue
- if parser.get("metadata", "type") in self._ignored_instance_container_types:
- continue
-
- instance_container = InstanceContainer(container_id)
-
- # Deserialize InstanceContainer by converting read data from bytes to string
- instance_container.deserialize(serialized, file_name = instance_container_file)
- container_type = instance_container.getMetaDataEntry("type")
- Job.yieldThread()
-
- #
- # IMPORTANT:
- # If an instance container (or maybe other type of container) exists, and user chooses "Create New",
- # we need to rename this container and all references to it, and changing those references are VERY
- # HARD.
- #
- if container_type in self._ignored_instance_container_types:
- # Ignore certain instance container types
- Logger.log("w", "Ignoring instance container [%s] with type [%s]", container_id, container_type)
- continue
- elif container_type == "user":
- # Check if quality changes already exists.
- user_containers = self._container_registry.findInstanceContainers(id = container_id)
- if not user_containers:
- containers_to_add.append(instance_container)
- else:
- if self._resolve_strategies["machine"] == "override" or self._resolve_strategies["machine"] is None:
- instance_container = user_containers[0]
- instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"),
- file_name = instance_container_file)
- instance_container.setDirty(True)
- elif self._resolve_strategies["machine"] == "new":
- # The machine is going to get a spiffy new name, so ensure that the id's of user settings match.
- old_extruder_id = instance_container.getMetaDataEntry("extruder", None)
- if old_extruder_id:
- new_extruder_id = extruder_stack_id_map[old_extruder_id]
- new_id = new_extruder_id + "_current_settings"
- instance_container.setMetaDataEntry("id", new_id)
- instance_container.setName(new_id)
- instance_container.setMetaDataEntry("extruder", new_extruder_id)
- containers_to_add.append(instance_container)
-
- machine_id = instance_container.getMetaDataEntry("machine", None)
- if machine_id:
- new_machine_id = self.getNewId(machine_id)
- new_id = new_machine_id + "_current_settings"
- instance_container.setMetaDataEntry("id", new_id)
- instance_container.setName(new_id)
- instance_container.setMetaDataEntry("machine", new_machine_id)
- containers_to_add.append(instance_container)
- user_instance_containers.append(instance_container)
- elif container_type in ("quality_changes", "definition_changes"):
- # Check if quality changes already exists.
- changes_containers = self._container_registry.findInstanceContainers(id = container_id)
- if not changes_containers:
- # no existing containers with the same ID, so we can safely add the new one
- containers_to_add.append(instance_container)
- else:
- # we have found existing container with the same ID, so we need to resolve according to the
- # selected strategy.
- if self._resolve_strategies[container_type] == "override":
- instance_container = changes_containers[0]
- instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"),
- file_name = instance_container_file)
- instance_container.setDirty(True)
-
- elif self._resolve_strategies[container_type] == "new":
- # TODO: how should we handle the case "new" for quality_changes and definition_changes?
-
- instance_container.setName(self._container_registry.uniqueName(instance_container.getName()))
- new_changes_container_id = self.getNewId(instance_container.getId())
- instance_container.setMetaDataEntry("id", new_changes_container_id)
-
- # TODO: we don't know the following is correct or not, need to verify
- # AND REFACTOR!!!
- if self._resolve_strategies["machine"] == "new":
- # The machine is going to get a spiffy new name, so ensure that the id's of user settings match.
- old_extruder_id = instance_container.getMetaDataEntry("extruder", None)
- # Note that in case of a quality_changes extruder means the definition id of the extruder stack
- # For the user settings, it means the actual extruder stack id it's assigned to.
- if old_extruder_id and old_extruder_id in extruder_stack_id_map:
- new_extruder_id = extruder_stack_id_map[old_extruder_id]
- instance_container.setMetaDataEntry("extruder", new_extruder_id)
-
- machine_id = instance_container.getMetaDataEntry("machine", None)
- if machine_id:
- new_machine_id = self.getNewId(machine_id)
- instance_container.setMetaDataEntry("machine", new_machine_id)
-
- containers_to_add.append(instance_container)
-
- elif self._resolve_strategies[container_type] is None:
- # The ID already exists, but nothing in the values changed, so do nothing.
- pass
- quality_and_definition_changes_instance_containers.append(instance_container)
- if container_type == "quality_changes":
- quality_changes_instance_containers.append(instance_container)
-
- if container_type == "definition_changes":
- definition_changes_extruder_count = instance_container.getProperty("machine_extruder_count", "value")
- if definition_changes_extruder_count is not None:
- machine_extruder_count = definition_changes_extruder_count
-
- else:
- existing_container = self._container_registry.findInstanceContainersMetadata(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:
- self._container_registry.addContainer(container)
- container.setDirty(True)
- containers_added.append(container)
-
- # Get the stack(s) saved in the workspace.
- Logger.log("d", "Workspace loading is checking stacks containers...")
-
- # load global stack file
- try:
- stack = None
-
- if self._resolve_strategies["machine"] == "override":
- container_stacks = self._container_registry.findContainerStacks(id = global_stack_id_original)
- stack = container_stacks[0]
-
- # HACK
- # There is a machine, check if it has authentication data. If so, keep that data.
- network_authentication_id = stack.getMetaDataEntry("network_authentication_id")
- network_authentication_key = stack.getMetaDataEntry("network_authentication_key")
- stack.deserialize(archive.open(global_stack_file).read().decode("utf-8"), file_name = global_stack_file)
- if network_authentication_id:
- stack.addMetaDataEntry("network_authentication_id", network_authentication_id)
- if network_authentication_key:
- stack.addMetaDataEntry("network_authentication_key", network_authentication_key)
-
- elif self._resolve_strategies["machine"] == "new":
- # create a new global stack
- 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"),
- file_name = global_stack_file)
-
- # Ensure a unique ID and name
- stack.setMetaDataEntry("id", global_stack_id_new)
-
- # Only machines need a new name, stacks may be non-unique
- stack.setName(global_stack_name_new)
-
- container_stacks_added.append(stack)
- # self._container_registry.addContainer(stack)
- containers_added.append(stack)
- else:
- Logger.log("e", "Resolve strategy of %s for machine is not supported", self._resolve_strategies["machine"])
-
- # Create a new definition_changes container if it was empty
- if stack.definitionChanges == self._container_registry.getEmptyInstanceContainer():
- stack.setDefinitionChanges(CuraStackBuilder.createDefinitionChangesContainer(stack, stack.getId() + "_settings"))
- 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_added:
- self._container_registry.removeContainer(container.getId())
- return
-
- # load extruder stack files
- has_extruder_stack_files = len(extruder_stack_files) > 0
- empty_quality_container = self._container_registry.findInstanceContainers(id = "empty_quality")[0]
- empty_quality_changes_container = self._container_registry.findInstanceContainers(id = "empty_quality_changes")[0]
- try:
- for extruder_stack_file in extruder_stack_files:
- container_id = self._stripFileToId(extruder_stack_file)
- extruder_file_content = archive.open(extruder_stack_file, "r").read().decode("utf-8")
-
- if self._resolve_strategies["machine"] == "override":
- # deserialize new extruder stack over the current ones (if any)
- stack = self._overrideExtruderStack(global_stack, extruder_file_content, extruder_stack_file)
- if stack is None:
- continue
-
- elif self._resolve_strategies["machine"] == "new":
- new_id = extruder_stack_id_map[container_id]
- stack = ExtruderStack(new_id)
-
- # HACK: the global stack can have a new name, so we need to make sure that this extruder stack
- # references to the new name instead of the old one. Normally, this can be done after
- # deserialize() by setting the metadata, but in the case of ExtruderStack, deserialize()
- # also does addExtruder() to its machine stack, so we have to make sure that it's pointing
- # to the right machine BEFORE deserialization.
- extruder_config = configparser.ConfigParser(interpolation = None)
- extruder_config.read_string(extruder_file_content)
- extruder_config.set("metadata", "machine", global_stack_id_new)
- tmp_string_io = io.StringIO()
- extruder_config.write(tmp_string_io)
- extruder_file_content = tmp_string_io.getvalue()
-
- stack.deserialize(extruder_file_content, file_name = extruder_stack_file)
-
- # Ensure a unique ID and name
- stack.setMetaDataEntry("id", new_id)
-
- self._container_registry.addContainer(stack)
- extruder_stacks_added.append(stack)
- containers_added.append(stack)
- else:
- Logger.log("w", "Unknown resolve strategy: %s", self._resolve_strategies["machine"])
-
- # Create a new definition_changes container if it was empty
- if stack.definitionChanges == self._container_registry.getEmptyInstanceContainer():
- stack.setDefinitionChanges(CuraStackBuilder.createDefinitionChangesContainer(stack, stack.getId() + "_settings"))
-
- if stack.getMetaDataEntry("type") == "extruder_train":
- extruder_stacks.append(stack)
-
- # If not extruder stacks were saved in the project file (pre 3.1) create one manually
- # We re-use the container registry's addExtruderStackForSingleExtrusionMachine method for this
- if not extruder_stacks:
- # If we choose to override a machine but to create a new custom quality profile, the custom quality
- # profile is not immediately applied to the global_stack, so this fix for single extrusion machines
- # will use the current custom quality profile on the existing machine. The extra optional argument
- # in that function is used in this case to specify a new global stack quality_changes container so
- # the fix can correctly create and copy over the custom quality settings to the newly created extruder.
- new_global_quality_changes = None
- if self._resolve_strategies["quality_changes"] == "new" and len(quality_changes_instance_containers) > 0:
- new_global_quality_changes = quality_changes_instance_containers[0]
-
- # Depending if the strategy is to create a new or override, the ids must be or not be unique
- stack = self._container_registry.addExtruderStackForSingleExtrusionMachine(global_stack, "fdmextruder",
- new_global_quality_changes,
- create_new_ids = self._resolve_strategies["machine"] == "new")
- if new_global_quality_changes is not None:
- quality_changes_instance_containers.append(stack.qualityChanges)
- quality_and_definition_changes_instance_containers.append(stack.qualityChanges)
- if global_stack.quality.getId() in ("empty", "empty_quality"):
- stack.quality = empty_quality_container
- if self._resolve_strategies["machine"] == "override":
- # in case the extruder is newly created (for a single-extrusion machine), we need to override
- # the existing extruder stack.
- existing_extruder_stack = global_stack.extruders[stack.getMetaDataEntry("position")]
- for idx in range(len(_ContainerIndexes.IndexTypeMap)):
- existing_extruder_stack.replaceContainer(idx, stack._containers[idx], postpone_emit = True)
- extruder_stacks.append(existing_extruder_stack)
- else:
- 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 containers_added:
- self._container_registry.removeContainer(container.getId())
- return
-
- ## In case there is a new machine and once the extruders are created, the global stack is added to the registry,
- # otherwise the addContainers function in CuraContainerRegistry will create an extruder stack and then creating
- # useless files
- if self._resolve_strategies["machine"] == "new":
- self._container_registry.addContainer(global_stack)
-
- # Check quality profiles to make sure that if one stack has the "not supported" quality profile,
- # all others should have the same.
- #
- # This block code tries to fix the following problems in Cura 3.0 and earlier:
- # 1. The upgrade script can rename all "Not Supported" quality profiles to "empty_quality", but it cannot fix
- # the problem that the global stack the extruder stacks may have different quality profiles. The code
- # below loops over all stacks and make sure that if there is one stack with "Not Supported" profile, the
- # rest should also use the "Not Supported" profile.
- # 2. In earlier versions (at least 2.7 and 3.0), a wrong quality profile could be assigned to a stack. For
- # example, a UM3 can have a BB 0.8 variant with "aa04_pla_fast" quality profile enabled. To fix this,
- # in the code below we also check the actual available quality profiles for the machine.
- #
- has_not_supported = False
- for stack in [global_stack] + extruder_stacks:
- if stack.quality.getId() in ("empty", "empty_quality"):
- has_not_supported = True
- break
-
- # We filter out extruder stacks that are not actually used, for example the UM3 and custom FDM printer extruder count setting.
- extruder_stacks_in_use = extruder_stacks
- if machine_extruder_count is not None:
- extruder_stacks_in_use = extruder_stacks[:machine_extruder_count]
-
- quality_manager = CuraApplication.getInstance()._quality_manager
- all_quality_groups = quality_manager.getQualityGroups(global_stack)
- available_quality_types = [qt for qt, qg in all_quality_groups.items() if qg.is_available]
- if not has_not_supported:
- has_not_supported = not available_quality_types
-
- quality_has_been_changed = False
-
- if has_not_supported:
- for stack in [global_stack] + extruder_stacks_in_use:
- stack.replaceContainer(_ContainerIndexes.Quality, empty_quality_container)
- stack.replaceContainer(_ContainerIndexes.QualityChanges, empty_quality_changes_container)
- quality_has_been_changed = True
-
- else:
- # The machine in the project has non-empty quality and there are usable qualities for this machine.
- # We need to check if the current quality_type is still usable for this machine, if not, then the quality
- # will be reset to the "preferred quality" if present, otherwise "normal".
- if global_stack.quality.getMetaDataEntry("quality_type") not in available_quality_types:
- # We are here because the quality_type specified in the project is not supported any more,
- # so we need to switch it to the "preferred quality" if present, otherwise "normal".
- quality_has_been_changed = True
-
- # find the preferred quality
- preferred_quality_id = global_stack.getMetaDataEntry("preferred_quality", None)
- if preferred_quality_id is not None:
- definition_id = global_stack.definition.getId()
- definition_id = global_stack.definition.getMetaDataEntry("quality_definition", definition_id)
- if not parseBool(global_stack.getMetaDataEntry("has_machine_quality", "False")):
- definition_id = "fdmprinter"
-
- containers = self._container_registry.findInstanceContainers(id = preferred_quality_id,
- type = "quality",
- definition = definition_id)
- containers = [c for c in containers if not c.getMetaDataEntry("material", "")]
- if containers:
- global_stack.quality = containers[0]
- global_stack.qualityChanges = empty_quality_changes_container
- # also find the quality containers for the extruders
- for extruder_stack in extruder_stacks_in_use:
- search_criteria = {"id": preferred_quality_id,
- "type": "quality",
- "definition": definition_id}
- if global_stack.getMetaDataEntry("has_machine_materials") and extruder_stack.material.getId() not in ("empty", "empty_material"):
- search_criteria["material"] = extruder_stack.material.getId()
- containers = self._container_registry.findInstanceContainers(**search_criteria)
- if containers:
- extruder_stack.quality = containers[0]
- extruder_stack.qualityChanges = empty_quality_changes_container
- else:
- Logger.log("e", "Cannot find preferred quality for extruder [%s].", extruder_stack.getId())
-
- else:
- # we cannot find the preferred quality. THIS SHOULD NOT HAPPEN
- Logger.log("e", "Cannot find the preferred quality for machine [%s]", global_stack.getId())
- else:
- # The quality_type specified in the project file is usable, but the quality container itself may not
- # be correct. For example, for UM2, the quality container can be "draft" while it should be "um2_draft"
- # instead.
- # In this code branch, we try to fix those incorrect quality containers.
- #
- # ***IMPORTANT***: We only do this fix for single-extrusion machines.
- # We will first find the correct quality profile for the extruder, then apply the same
- # quality profile for the global stack.
- #
- if len(extruder_stacks) == 1:
- extruder_stack = extruder_stacks[0]
-
- search_criteria = {"type": "quality", "quality_type": global_stack.quality.getMetaDataEntry("quality_type")}
- search_criteria["definition"] = global_stack.definition.getId()
- if not parseBool(global_stack.getMetaDataEntry("has_machine_quality", "False")):
- search_criteria["definition"] = "fdmprinter"
-
- if global_stack.getMetaDataEntry("has_machine_materials") and extruder_stack.material.getId() not in ("empty", "empty_material"):
- search_criteria["material"] = extruder_stack.material.getId()
- containers = self._container_registry.findInstanceContainers(**search_criteria)
- if containers:
- new_quality_container = containers[0]
- extruder_stack.quality = new_quality_container
- global_stack.quality = new_quality_container
-
- # Now we are checking if the quality in the extruder stacks is the same as in the global. In other case,
- # the quality is set to be the same.
- definition_id = global_stack.definition.getId()
- definition_id = global_stack.definition.getMetaDataEntry("quality_definition", definition_id)
- if not parseBool(global_stack.getMetaDataEntry("has_machine_quality", "False")):
- definition_id = "fdmprinter"
-
- for extruder_stack in extruder_stacks_in_use:
-
- # If the quality is different in the stacks, then the quality in the global stack is trusted
- if extruder_stack.quality.getMetaDataEntry("quality_type") != global_stack.quality.getMetaDataEntry("quality_type"):
- search_criteria = {"id": global_stack.quality.getId(),
- "type": "quality",
- "definition": definition_id}
- if global_stack.getMetaDataEntry("has_machine_materials") and extruder_stack.material.getId() not in ("empty", "empty_material"):
- search_criteria["material"] = extruder_stack.material.getId()
- containers = self._container_registry.findInstanceContainers(**search_criteria)
- if containers:
- extruder_stack.quality = containers[0]
- extruder_stack.qualityChanges = empty_quality_changes_container
- else:
- Logger.log("e", "Cannot find a suitable quality for extruder [%s].", extruder_stack.getId())
-
- quality_has_been_changed = True
-
- else:
- Logger.log("i", "The quality is the same for the global and the extruder stack [%s]", global_stack.quality.getId())
-
- # Replacing the old containers if resolve is "new".
- # When resolve is "new", some containers will get renamed, so all the other containers that reference to those
- # MUST get updated too.
- #
- if self._resolve_strategies["machine"] == "new":
- # A new machine was made, but it was serialized with the wrong user container. Fix that now.
- for container in user_instance_containers:
- # replacing the container ID for user instance containers for the extruders
- extruder_id = container.getMetaDataEntry("extruder", None)
- if extruder_id:
- for extruder in extruder_stacks:
- if extruder.getId() == extruder_id:
- extruder.userChanges = container
- continue
-
- # replacing the container ID for user instance containers for the machine
- machine_id = container.getMetaDataEntry("machine", None)
- if machine_id:
- if global_stack.getId() == machine_id:
- global_stack.userChanges = container
- continue
-
- changes_container_types = ("quality_changes", "definition_changes")
- if quality_has_been_changed:
- # DO NOT replace quality_changes if the current quality_type is not supported
- changes_container_types = ("definition_changes",)
- for changes_container_type in changes_container_types:
- if self._resolve_strategies[changes_container_type] == "new":
- # Quality changes needs to get a new ID, added to registry and to the right stacks
- for each_changes_container in quality_and_definition_changes_instance_containers:
- # NOTE: The renaming and giving new IDs are possibly redundant because they are done in the
- # instance container loading part.
- new_id = each_changes_container.getId()
-
- # Find the old (current) changes container in the global stack
- if changes_container_type == "quality_changes":
- old_container = global_stack.qualityChanges
- elif changes_container_type == "definition_changes":
- old_container = global_stack.definitionChanges
-
- # sanity checks
- # NOTE: The following cases SHOULD NOT happen!!!!
- if old_container.getId() in ("empty_quality_changes", "empty_definition_changes", "empty"):
- Logger.log("e", "We try to get [%s] from the global stack [%s] but we got None instead!",
- changes_container_type, global_stack.getId())
- continue
-
- # Replace the quality/definition changes container if it's in the GlobalStack
- # NOTE: we can get an empty container here, but the IDs will not match,
- # so this comparison is fine.
- if self._id_mapping.get(old_container.getId()) == new_id:
- if changes_container_type == "quality_changes":
- global_stack.qualityChanges = each_changes_container
- elif changes_container_type == "definition_changes":
- global_stack.definitionChanges = each_changes_container
- continue
-
- # Replace the quality/definition changes container if it's in one of the ExtruderStacks
- # Only apply the change if we have loaded extruder stacks from the project
- if has_extruder_stack_files:
- for each_extruder_stack in extruder_stacks:
- changes_container = None
- if changes_container_type == "quality_changes":
- changes_container = each_extruder_stack.qualityChanges
- elif changes_container_type == "definition_changes":
- changes_container = each_extruder_stack.definitionChanges
-
- # sanity checks
- # NOTE: The following cases SHOULD NOT happen!!!!
- if changes_container.getId() in ("empty_quality_changes", "empty_definition_changes", "empty"):
- Logger.log("e", "We try to get [%s] from the extruder stack [%s] but we got None instead!",
- changes_container_type, each_extruder_stack.getId())
- continue
-
- # NOTE: we can get an empty container here, but the IDs will not match,
- # so this comparison is fine.
- if self._id_mapping.get(changes_container.getId()) == new_id:
- if changes_container_type == "quality_changes":
- each_extruder_stack.qualityChanges = each_changes_container
- elif changes_container_type == "definition_changes":
- each_extruder_stack.definitionChanges = each_changes_container
-
- if self._resolve_strategies["material"] == "new":
- # the actual material instance container can have an ID such as
- # __
- # which cannot be determined immediately, so here we use a HACK to find the right new material
- # instance ID:
- # - get the old material IDs for all material
- # - find the old material with the longest common prefix in ID, that's the old material
- # - update the name by replacing the old prefix with the new
- # - find the new material container and set it to the stack
- old_to_new_material_dict = {}
- for each_material in material_containers:
- # find the material's old name
- for old_id, new_id in self._id_mapping.items():
- if each_material.getId() == new_id:
- old_to_new_material_dict[old_id] = each_material
- break
-
- # replace old material in global and extruder stacks with new
- self._replaceStackMaterialWithNew(global_stack, old_to_new_material_dict)
- if extruder_stacks:
- for each_extruder_stack in extruder_stacks:
- self._replaceStackMaterialWithNew(each_extruder_stack, old_to_new_material_dict)
-
- if extruder_stacks:
- for stack in extruder_stacks:
- ExtruderManager.getInstance().registerExtruder(stack, global_stack.getId())
+ # Prepare the machine
+ self._applyChangesToMachine(global_stack, extruder_stack_dict)
Logger.log("d", "Workspace loading is notifying rest of the code of changes...")
-
- if self._resolve_strategies["machine"] == "new":
- for stack in extruder_stacks:
- stack.setNextStack(global_stack)
- stack.containersChanged.emit(stack.getTop())
- else:
- CuraApplication.getInstance().getMachineManager().activeQualityChanged.emit()
-
# Actually change the active machine.
#
# This is scheduled for later is because it depends on the Variant/Material/Qualitiy Managers to have the latest
@@ -1088,7 +714,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# function is running on the main thread (Qt thread), although those "changed" signals have been emitted, but
# they won't take effect until this function is done.
# To solve this, we schedule _updateActiveMachine() for later so it will have the latest data.
- CuraApplication.getInstance().callLater(self._updateActiveMachine, global_stack)
+ self._updateActiveMachine(global_stack)
# Load all the nodes / meshdata of the workspace
nodes = self._3mf_mesh_reader.read(file_name)
@@ -1101,69 +727,285 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
self.setWorkspaceName(base_file_name)
return nodes
+ def _processQualityChanges(self, global_stack):
+ if self._machine_info.quality_changes_info is None:
+ return
+
+ application = CuraApplication.getInstance()
+ quality_manager = application.getQualityManager()
+
+ # If we have custom profiles, load them
+ quality_changes_name = self._machine_info.quality_changes_info.name
+ if self._machine_info.quality_changes_info is not None:
+ Logger.log("i", "Loading custom profile [%s] from project file",
+ self._machine_info.quality_changes_info.name)
+
+ # Get the correct extruder definition IDs for quality changes
+ from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
+ machine_definition_id_for_quality = getMachineDefinitionIDForQualitySearch(global_stack.definition)
+ machine_definition_for_quality = self._container_registry.findDefinitionContainers(id = machine_definition_id_for_quality)[0]
+
+ quality_changes_info = self._machine_info.quality_changes_info
+ quality_changes_quality_type = quality_changes_info.global_info.parser["metadata"]["quality_type"]
+
+ quality_changes_name = quality_changes_info.name
+ create_new = self._resolve_strategies.get("quality_changes") != "override"
+ if create_new:
+ container_info_dict = {None: self._machine_info.quality_changes_info.global_info}
+ container_info_dict.update(quality_changes_info.extruder_info_dict)
+
+ quality_changes_name = self._container_registry.uniqueName(quality_changes_name)
+ for position, container_info in container_info_dict.items():
+ extruder_stack = None
+ if position is not None:
+ extruder_stack = global_stack.extruders[position]
+ container = quality_manager._createQualityChanges(quality_changes_quality_type,
+ quality_changes_name,
+ global_stack, extruder_stack)
+ container_info.container = container
+ container.setDirty(True)
+ self._container_registry.addContainer(container)
+
+ Logger.log("d", "Created new quality changes container [%s]", container.getId())
+
+ else:
+ # Find the existing containers
+ quality_changes_containers = self._container_registry.findInstanceContainers(name = quality_changes_name,
+ type = "quality_changes")
+ for container in quality_changes_containers:
+ extruder_position = container.getMetaDataEntry("position")
+ if extruder_position is None:
+ quality_changes_info.global_info.container = container
+ else:
+ if extruder_position not in quality_changes_info.extruder_info_dict:
+ quality_changes_info.extruder_info_dict[extruder_position] = ContainerInfo(None, None, None)
+ container_info = quality_changes_info.extruder_info_dict[extruder_position]
+ container_info.container = container
+
+ # If there is no quality changes for any extruder, create one.
+ if not quality_changes_info.extruder_info_dict:
+ container_info = ContainerInfo(None, None, None)
+ quality_changes_info.extruder_info_dict["0"] = container_info
+ extruder_stack = global_stack.extruders["0"]
+
+ container = quality_manager._createQualityChanges(quality_changes_quality_type, quality_changes_name,
+ global_stack, extruder_stack)
+ container_info.container = container
+ container.setDirty(True)
+ self._container_registry.addContainer(container)
+
+ Logger.log("d", "Created new quality changes container [%s]", container.getId())
+
+ # Clear all existing containers
+ quality_changes_info.global_info.container.clear()
+ for container_info in quality_changes_info.extruder_info_dict.values():
+ container_info.container.clear()
+
+ # Loop over everything and override the existing containers
+ global_info = quality_changes_info.global_info
+ global_info.container.clear() # Clear all
+ for key, value in global_info.parser["values"].items():
+ if not machine_definition_for_quality.getProperty(key, "settable_per_extruder"):
+ global_info.container.setProperty(key, "value", value)
+ else:
+ quality_changes_info.extruder_info_dict["0"].container.setProperty(key, "value", value)
+
+ for position, container_info in quality_changes_info.extruder_info_dict.items():
+ if container_info.parser is None:
+ continue
+
+ if container_info.container is None:
+ extruder_stack = global_stack.extruders[position]
+ container = quality_manager._createQualityChanges(quality_changes_quality_type, quality_changes_name,
+ global_stack, extruder_stack)
+ container_info.container = container
+
+ for key, value in container_info.parser["values"].items():
+ container_info.container.setProperty(key, "value", value)
+
+ self._machine_info.quality_changes_info.name = quality_changes_name
+
+ def _clearStack(self, stack):
+ application = CuraApplication.getInstance()
+
+ stack.definitionChanges.clear()
+ stack.variant = application.empty_variant_container
+ stack.material = application.empty_material_container
+ stack.quality = application.empty_quality_container
+ stack.qualityChanges = application.empty_quality_changes_container
+ stack.userChanges.clear()
+
+ def _applyDefinitionChanges(self, global_stack, extruder_stack_dict):
+ values_to_set_for_extruders = {}
+ if self._machine_info.definition_changes_info is not None:
+ parser = self._machine_info.definition_changes_info.parser
+ for key, value in parser["values"].items():
+ if global_stack.getProperty(key, "settable_per_extruder"):
+ values_to_set_for_extruders[key] = value
+ else:
+ global_stack.definitionChanges.setProperty(key, "value", value)
+
+ for position, extruder_stack in extruder_stack_dict.items():
+ if position not in self._machine_info.extruder_info_dict:
+ continue
+
+ extruder_info = self._machine_info.extruder_info_dict[position]
+ if extruder_info.definition_changes_info is None:
+ continue
+ parser = extruder_info.definition_changes_info.parser
+ for key, value in values_to_set_for_extruders.items():
+ extruder_stack.definitionChanges.setProperty(key, "value", value)
+ if parser is not None:
+ for key, value in parser["values"].items():
+ extruder_stack.definitionChanges.setProperty(key, "value", value)
+
+ def _applyUserChanges(self, global_stack, extruder_stack_dict):
+ values_to_set_for_extruder_0 = {}
+ if self._machine_info.user_changes_info is not None:
+ parser = self._machine_info.user_changes_info.parser
+ for key, value in parser["values"].items():
+ if global_stack.getProperty(key, "settable_per_extruder"):
+ values_to_set_for_extruder_0[key] = value
+ else:
+ global_stack.userChanges.setProperty(key, "value", value)
+
+ for position, extruder_stack in extruder_stack_dict.items():
+ if position not in self._machine_info.extruder_info_dict:
+ continue
+
+ extruder_info = self._machine_info.extruder_info_dict[position]
+ if extruder_info.user_changes_info is not None:
+ parser = self._machine_info.extruder_info_dict[position].user_changes_info.parser
+ if position == "0":
+ for key, value in values_to_set_for_extruder_0.items():
+ extruder_stack.userChanges.setProperty(key, "value", value)
+ if parser is not None:
+ for key, value in parser["values"].items():
+ extruder_stack.userChanges.setProperty(key, "value", value)
+
+ def _applyVariants(self, global_stack, extruder_stack_dict):
+ application = CuraApplication.getInstance()
+ variant_manager = application.getVariantManager()
+
+ if self._machine_info.variant_info is not None:
+ parser = self._machine_info.variant_info.parser
+ variant_name = parser["general"]["name"]
+
+ from cura.Machines.VariantManager import VariantType
+ variant_type = VariantType.BUILD_PLATE
+
+ node = variant_manager.getVariantNode(global_stack.definition.getId(), variant_name, variant_type)
+ if node is not None:
+ global_stack.variant = node.getContainer()
+
+ for position, extruder_stack in extruder_stack_dict.items():
+ if position not in self._machine_info.extruder_info_dict:
+ continue
+ extruder_info = self._machine_info.extruder_info_dict[position]
+ if extruder_info.variant_info is None:
+ continue
+ parser = extruder_info.variant_info.parser
+
+ variant_name = parser["general"]["name"]
+ from cura.Machines.VariantManager import VariantType
+ variant_type = VariantType.NOZZLE
+
+ node = variant_manager.getVariantNode(global_stack.definition.getId(), variant_name, variant_type)
+ if node is not None:
+ extruder_stack.variant = node.getContainer()
+
+ def _applyMaterials(self, global_stack, extruder_stack_dict):
+ application = CuraApplication.getInstance()
+ material_manager = application.getMaterialManager()
+
+ # Force update lookup tables first
+ material_manager.initialize()
+
+ for position, extruder_stack in extruder_stack_dict.items():
+ if position not in self._machine_info.extruder_info_dict:
+ continue
+ extruder_info = self._machine_info.extruder_info_dict[position]
+ if extruder_info.root_material_id is None:
+ continue
+
+ root_material_id = extruder_info.root_material_id
+ root_material_id = self._old_new_materials.get(root_material_id, root_material_id)
+
+ # get material diameter of this extruder
+ machine_material_diameter = extruder_stack.materialDiameter
+ material_node = material_manager.getMaterialNode(global_stack.definition.getId(),
+ extruder_stack.variant.getName(),
+ machine_material_diameter,
+ root_material_id)
+ if material_node is not None:
+ extruder_stack.material = material_node.getContainer()
+
+ def _applyChangesToMachine(self, global_stack, extruder_stack_dict):
+ # Clear all first
+ self._clearStack(global_stack)
+ for extruder_stack in extruder_stack_dict.values():
+ self._clearStack(extruder_stack)
+
+ self._applyDefinitionChanges(global_stack, extruder_stack_dict)
+ self._applyUserChanges(global_stack, extruder_stack_dict)
+ self._applyVariants(global_stack, extruder_stack_dict)
+ self._applyMaterials(global_stack, extruder_stack_dict)
+
+ # prepare the quality to select
+ self._quality_changes_to_apply = None
+ self._quality_type_to_apply = None
+ if self._machine_info.quality_changes_info is not None:
+ self._quality_changes_to_apply = self._machine_info.quality_changes_info.name
+ else:
+ self._quality_type_to_apply = self._machine_info.quality_type
+
+ # Set enabled/disabled for extruders
+ for position, extruder_stack in extruder_stack_dict.items():
+ extruder_info = self._machine_info.extruder_info_dict.get(position)
+ if not extruder_info:
+ continue
+ if "enabled" not in extruder_stack.getMetaData():
+ extruder_stack.addMetaDataEntry("enabled", "True")
+ extruder_stack.setMetaDataEntry("enabled", str(extruder_info.enabled))
+
def _updateActiveMachine(self, global_stack):
# Actually change the active machine.
machine_manager = Application.getInstance().getMachineManager()
+ material_manager = Application.getInstance().getMaterialManager()
+ quality_manager = Application.getInstance().getQualityManager()
+
+ # Force update the lookup maps first
+ material_manager.initialize()
+ quality_manager.initialize()
+
machine_manager.setActiveMachine(global_stack.getId())
+ if self._quality_changes_to_apply:
+ quality_changes_group_dict = quality_manager.getQualityChangesGroups(global_stack)
+ if self._quality_changes_to_apply not in quality_changes_group_dict:
+ Logger.log("e", "Could not find quality_changes [%s]", self._quality_changes_to_apply)
+ return
+ quality_changes_group = quality_changes_group_dict[self._quality_changes_to_apply]
+ machine_manager.setQualityChangesGroup(quality_changes_group, no_dialog = True)
+ else:
+ self._quality_type_to_apply = self._quality_type_to_apply.lower()
+ quality_group_dict = quality_manager.getQualityGroups(global_stack)
+ if self._quality_type_to_apply in quality_group_dict:
+ quality_group = quality_group_dict[self._quality_type_to_apply]
+ else:
+ Logger.log("i", "Could not find quality type [%s], switch to default", self._quality_type_to_apply)
+ preferred_quality_type = global_stack.getMetaDataEntry("preferred_quality_type")
+ quality_group_dict = quality_manager.getQualityGroups(global_stack)
+ quality_group = quality_group_dict.get(preferred_quality_type)
+ if quality_group is None:
+ Logger.log("e", "Could not get preferred quality type [%s]", preferred_quality_type)
+
+ if quality_group is not None:
+ machine_manager.setQualityGroup(quality_group, no_dialog = True)
+
# Notify everything/one that is to notify about changes.
global_stack.containersChanged.emit(global_stack.getTop())
- ## HACK: Replaces the material container in the given stack with a newly created material container.
- # This function is used when the user chooses to resolve material conflicts by creating new ones.
- def _replaceStackMaterialWithNew(self, stack, old_new_material_dict):
- # The material containers in the project file are 'parent' material such as "generic_pla",
- # but a material container used in a global/extruder stack is a 'child' material,
- # such as "generic_pla_ultimaker3_AA_0.4", which can be formalised as the following:
- #
- # __
- #
- # In the project loading, when a user chooses to resolve material conflicts by creating new ones,
- # the old 'parent' material ID and the new 'parent' material ID are known, but not the child material IDs.
- # In this case, the global stack and the extruder stacks need to use the newly created material, but the
- # material containers they use are 'child' material. So, here, we need to find the right 'child' material for
- # the stacks.
- #
- # This hack approach works as follows:
- # - No matter there is a child material or not, the actual material we are looking for has the prefix
- # "", which is the old material name. For the material in a stack, we know that the new
- # material's ID will be "_blabla..", so we just need to replace the old material ID
- # with the new one to get the new 'child' material.
- # - Because the material containers have IDs such as "m #nn", if we use simple prefix matching, there can
- # be a problem in the following scenario:
- # - there are two materials in the project file, namely "m #1" and "m #11"
- # - the child materials in use are for example: "m #1_um3_aa04", "m #11_um3_aa04"
- # - if we only check for a simple prefix match, then "m #11_um3_aa04" will match with "m #1", but they
- # are not the same material
- # To avoid this, when doing the prefix matching, we use the result with the longest mactching prefix.
-
- # find the old material ID
- old_material_id_in_stack = stack.material.getId()
- best_matching_old_material_id = None
- best_matching_old_material_prefix_length = -1
- for old_parent_material_id in old_new_material_dict:
- if len(old_parent_material_id) < best_matching_old_material_prefix_length:
- continue
- if len(old_parent_material_id) <= len(old_material_id_in_stack):
- if old_parent_material_id == old_material_id_in_stack[0:len(old_parent_material_id)]:
- best_matching_old_material_prefix_length = len(old_parent_material_id)
- best_matching_old_material_id = old_parent_material_id
-
- if best_matching_old_material_id is None:
- Logger.log("w", "Cannot find any matching old material ID for stack [%s] material [%s]. Something can go wrong",
- stack.getId(), old_material_id_in_stack)
- return
-
- # find the new material container
- new_material_id = old_new_material_dict[best_matching_old_material_id].getId() + old_material_id_in_stack[len(best_matching_old_material_id):]
- new_material_containers = self._container_registry.findInstanceContainers(id = new_material_id, type = "material")
- if not new_material_containers:
- Logger.log("e", "Cannot find new material container [%s]", new_material_id)
- return
-
- # replace the material in the given stack
- stack.material = new_material_containers[0]
-
def _stripFileToId(self, file):
mime_type = MimeTypeDatabase.getMimeTypeForFile(file)
file = mime_type.stripExtension(file)
@@ -1174,7 +1016,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
## Get the list of ID's of all containers in a container stack by partially parsing it's serialized data.
def _getContainerIdListFromSerialized(self, serialized):
- parser = configparser.ConfigParser(interpolation=None, empty_lines_in_values=False)
+ parser = ConfigParser(interpolation=None, empty_lines_in_values=False)
parser.read_string(serialized)
container_ids = []
@@ -1195,7 +1037,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
return container_ids
def _getMachineNameFromSerializedStack(self, serialized):
- parser = configparser.ConfigParser(interpolation=None, empty_lines_in_values=False)
+ parser = ConfigParser(interpolation=None, empty_lines_in_values=False)
parser.read_string(serialized)
return parser["general"].get("name", "")
diff --git a/plugins/3MFReader/WorkspaceDialog.py b/plugins/3MFReader/WorkspaceDialog.py
index 5b474843d5..da682a6fc0 100644
--- a/plugins/3MFReader/WorkspaceDialog.py
+++ b/plugins/3MFReader/WorkspaceDialog.py
@@ -49,10 +49,10 @@ class WorkspaceDialog(QObject):
self._material_labels = []
self._extruders = []
self._objects_on_plate = False
+ self._is_printer_group = False
machineConflictChanged = pyqtSignal()
qualityChangesConflictChanged = pyqtSignal()
- definitionChangesConflictChanged = pyqtSignal()
materialConflictChanged = pyqtSignal()
numVisibleSettingsChanged = pyqtSignal()
activeModeChanged = pyqtSignal()
@@ -67,6 +67,16 @@ class WorkspaceDialog(QObject):
machineTypeChanged = pyqtSignal()
variantTypeChanged = pyqtSignal()
extrudersChanged = pyqtSignal()
+ isPrinterGroupChanged = pyqtSignal()
+
+ @pyqtProperty(bool, notify = isPrinterGroupChanged)
+ def isPrinterGroup(self) -> bool:
+ return self._is_printer_group
+
+ def setIsPrinterGroup(self, value: bool):
+ if value != self._is_printer_group:
+ self._is_printer_group = value
+ self.isPrinterGroupChanged.emit()
@pyqtProperty(str, notify=variantTypeChanged)
def variantType(self):
@@ -196,10 +206,6 @@ 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
@@ -229,18 +235,11 @@ 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
diff --git a/plugins/3MFReader/WorkspaceDialog.qml b/plugins/3MFReader/WorkspaceDialog.qml
index 5418dcef6d..58d881c915 100644
--- a/plugins/3MFReader/WorkspaceDialog.qml
+++ b/plugins/3MFReader/WorkspaceDialog.qml
@@ -108,7 +108,22 @@ UM.Dialog
text: catalog.i18nc("@info:tooltip", "How should the conflict in the machine be resolved?")
ComboBox
{
- model: resolveStrategiesModel
+ model: ListModel
+ {
+ Component.onCompleted:
+ {
+ append({"key": "override", "label": catalog.i18nc("@action:ComboBox option", "Update") + " " + manager.machineName});
+ append({"key": "new", "label": catalog.i18nc("@action:ComboBox option", "Create new")});
+ }
+ }
+ Connections
+ {
+ target: manager
+ onMachineNameChanged:
+ {
+ machineResolveComboBox.model.get(0).label = catalog.i18nc("@action:ComboBox option", "Update") + " " + manager.machineName;
+ }
+ }
textRole: "label"
id: machineResolveComboBox
width: parent.width
@@ -141,7 +156,7 @@ UM.Dialog
height: childrenRect.height
Label
{
- text: catalog.i18nc("@action:label", "Name")
+ text: catalog.i18nc("@action:label", manager.isPrinterGroup ? "Printer Group" : "Printer Name")
width: (parent.width / 3) | 0
}
Label
diff --git a/plugins/3MFWriter/ThreeMFWorkspaceWriter.py b/plugins/3MFWriter/ThreeMFWorkspaceWriter.py
index 507274d355..3f5e69317e 100644
--- a/plugins/3MFWriter/ThreeMFWorkspaceWriter.py
+++ b/plugins/3MFWriter/ThreeMFWorkspaceWriter.py
@@ -1,14 +1,15 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from UM.Workspace.WorkspaceWriter import WorkspaceWriter
+import configparser
+from io import StringIO
+import zipfile
+
from UM.Application import Application
+from UM.Logger import Logger
from UM.Preferences import Preferences
from UM.Settings.ContainerRegistry import ContainerRegistry
-from cura.Settings.ExtruderManager import ExtruderManager
-import zipfile
-from io import StringIO
-import configparser
+from UM.Workspace.WorkspaceWriter import WorkspaceWriter
class ThreeMFWorkspaceWriter(WorkspaceWriter):
@@ -16,7 +17,10 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter):
super().__init__()
def write(self, stream, nodes, mode=WorkspaceWriter.OutputMode.BinaryMode):
- mesh_writer = Application.getInstance().getMeshFileHandler().getWriter("3MFWriter")
+ application = Application.getInstance()
+ machine_manager = application.getMachineManager()
+
+ mesh_writer = application.getMeshFileHandler().getWriter("3MFWriter")
if not mesh_writer: # We need to have the 3mf mesh writer, otherwise we can't save the entire workspace
return False
@@ -29,17 +33,17 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter):
if archive is None: # This happens if there was no mesh data to write.
archive = zipfile.ZipFile(stream, "w", compression = zipfile.ZIP_DEFLATED)
- global_container_stack = Application.getInstance().getGlobalContainerStack()
+ global_stack = machine_manager.activeMachine
# Add global container stack data to the archive.
- self._writeContainerToArchive(global_container_stack, archive)
+ self._writeContainerToArchive(global_stack, archive)
# Also write all containers in the stack to the file
- for container in global_container_stack.getContainers():
+ for container in global_stack.getContainers():
self._writeContainerToArchive(container, archive)
# Check if the machine has extruders and save all that data as well.
- for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(global_container_stack.getId()):
+ for extruder_stack in global_stack.extruders.values():
self._writeContainerToArchive(extruder_stack, archive)
for container in extruder_stack.getContainers():
self._writeContainerToArchive(container, archive)
@@ -59,9 +63,9 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter):
version_file = zipfile.ZipInfo("Cura/version.ini")
version_config_parser = configparser.ConfigParser(interpolation = None)
version_config_parser.add_section("versions")
- version_config_parser.set("versions", "cura_version", Application.getInstance().getVersion())
- version_config_parser.set("versions", "build_type", Application.getInstance().getBuildType())
- version_config_parser.set("versions", "is_debug_mode", str(Application.getInstance().getIsDebugMode()))
+ version_config_parser.set("versions", "cura_version", application.getVersion())
+ version_config_parser.set("versions", "build_type", application.getBuildType())
+ version_config_parser.set("versions", "is_debug_mode", str(application.getIsDebugMode()))
version_file_string = StringIO()
version_config_parser.write(version_file_string)
@@ -85,7 +89,8 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter):
# Some containers have a base file, which should then be the file to use.
if "base_file" in container.getMetaData():
base_file = container.getMetaDataEntry("base_file")
- container = ContainerRegistry.getInstance().findContainers(id = base_file)[0]
+ if base_file != container.getId():
+ container = ContainerRegistry.getInstance().findContainers(id = base_file)[0]
file_name = "Cura/%s.%s" % (container.getId(), file_suffix)
diff --git a/plugins/ChangeLogPlugin/ChangeLog.txt b/plugins/ChangeLogPlugin/ChangeLog.txt
index 6b394f1e2e..8a031c9eae 100755
--- a/plugins/ChangeLogPlugin/ChangeLog.txt
+++ b/plugins/ChangeLogPlugin/ChangeLog.txt
@@ -1,3 +1,9 @@
+[3.2.1]
+*Bug fixes
+- Fixed issues where Cura crashes on startup and loading profiles
+- Updated translations
+- Fixed an issue where the text would not render properly
+
[3.2.0]
*Tree support
Experimental tree-like support structure that uses ‘branches’ to support prints. Branches ‘grow’ and multiply towards the model, with fewer contact points than alternative support methods. This results in better surface finishes for organic-shaped prints.
diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py
index 3982a0ad06..e55abe59a2 100755
--- a/plugins/CuraEngineBackend/CuraEngineBackend.py
+++ b/plugins/CuraEngineBackend/CuraEngineBackend.py
@@ -10,7 +10,6 @@ from UM.Logger import Logger
from UM.Message import Message
from UM.PluginRegistry import PluginRegistry
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
@@ -32,7 +31,11 @@ import Arcus
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
+
class CuraEngineBackend(QObject, Backend):
+
+ backendError = Signal()
+
## Starts the back-end plug-in.
#
# This registers all the signal listeners and prepares for communication
@@ -59,23 +62,26 @@ class CuraEngineBackend(QObject, Backend):
default_engine_location = execpath
break
+ self._application = Application.getInstance()
+ self._multi_build_plate_model = None
+ self._machine_error_checker = None
+
if not default_engine_location:
raise EnvironmentError("Could not find CuraEngine")
- Logger.log("i", "Found CuraEngine at: %s" %(default_engine_location))
+ Logger.log("i", "Found CuraEngine at: %s", default_engine_location)
default_engine_location = os.path.abspath(default_engine_location)
Preferences.getInstance().addPreference("backend/location", default_engine_location)
# Workaround to disable layer view processing if layer view is not active.
self._layer_view_active = False
- Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged)
- Application.getInstance().getMultiBuildPlateModel().activeBuildPlateChanged.connect(self._onActiveViewChanged)
self._onActiveViewChanged()
+
self._stored_layer_data = []
self._stored_optimized_layer_data = {} # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob
- self._scene = Application.getInstance().getController().getScene()
+ self._scene = self._application.getController().getScene()
self._scene.sceneChanged.connect(self._onSceneChanged)
# Triggers for auto-slicing. Auto-slicing is triggered as follows:
@@ -83,18 +89,10 @@ class CuraEngineBackend(QObject, Backend):
# - whenever there is a value change, we start the timer
# - sometimes an error check can get scheduled for a value change, in that case, we ONLY want to start the
# auto-slicing timer when that error check is finished
- # If there is an error check, it will set the "_is_error_check_scheduled" flag, stop the auto-slicing timer,
- # and only wait for the error check to be finished to start the auto-slicing timer again.
+ # If there is an error check, stop the auto-slicing timer, and only wait for the error check to be finished
+ # to start the auto-slicing timer again.
#
self._global_container_stack = None
- Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
- self._onGlobalStackChanged()
-
- Application.getInstance().stacksValidationFinished.connect(self._onStackErrorCheckFinished)
-
- # A flag indicating if an error check was scheduled
- # If so, we will stop the auto-slice timer and start upon the error check
- self._is_error_check_scheduled = False
# Listeners for receiving messages from the back-end.
self._message_handlers["cura.proto.Layer"] = self._onLayerMessage
@@ -120,13 +118,6 @@ class CuraEngineBackend(QObject, Backend):
self._last_num_objects = defaultdict(int) # Count number of objects to see if there is something changed
self._postponed_scene_change_sources = [] # scene change is postponed (by a tool)
- self.backendQuit.connect(self._onBackendQuit)
- self.backendConnected.connect(self._onBackendConnected)
-
- # When a tool operation is in progress, don't slice. So we need to listen for tool operations.
- Application.getInstance().getController().toolOperationStarted.connect(self._onToolOperationStarted)
- Application.getInstance().getController().toolOperationStopped.connect(self._onToolOperationStopped)
-
self._slice_start_time = None
Preferences.getInstance().addPreference("general/auto_slice", True)
@@ -141,6 +132,30 @@ class CuraEngineBackend(QObject, Backend):
self.determineAutoSlicing()
Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged)
+ self._application.initializationFinished.connect(self.initialize)
+
+ def initialize(self):
+ self._multi_build_plate_model = self._application.getMultiBuildPlateModel()
+
+ self._application.getController().activeViewChanged.connect(self._onActiveViewChanged)
+ self._multi_build_plate_model.activeBuildPlateChanged.connect(self._onActiveViewChanged)
+
+ self._application.globalContainerStackChanged.connect(self._onGlobalStackChanged)
+ self._onGlobalStackChanged()
+
+ # extruder enable / disable. Actually wanted to use machine manager here, but the initialization order causes it to crash
+ ExtruderManager.getInstance().extrudersChanged.connect(self._extruderChanged)
+
+ self.backendQuit.connect(self._onBackendQuit)
+ self.backendConnected.connect(self._onBackendConnected)
+
+ # When a tool operation is in progress, don't slice. So we need to listen for tool operations.
+ self._application.getController().toolOperationStarted.connect(self._onToolOperationStarted)
+ self._application.getController().toolOperationStopped.connect(self._onToolOperationStopped)
+
+ self._machine_error_checker = self._application.getMachineErrorChecker()
+ self._machine_error_checker.errorCheckFinished.connect(self._onStackErrorCheckFinished)
+
## Terminate the engine process.
#
# This function should terminate the engine process.
@@ -209,7 +224,11 @@ class CuraEngineBackend(QObject, Backend):
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
build_plate_to_be_sliced = self._build_plates_to_be_sliced.pop(0)
Logger.log("d", "Going to slice build plate [%s]!" % build_plate_to_be_sliced)
- num_objects = self._numObjects()
+ num_objects = self._numObjectsPerBuildPlate()
+
+ self._stored_layer_data = []
+ self._stored_optimized_layer_data[build_plate_to_be_sliced] = []
+
if build_plate_to_be_sliced not in num_objects or num_objects[build_plate_to_be_sliced] == 0:
self._scene.gcode_dict[build_plate_to_be_sliced] = []
Logger.log("d", "Build plate %s has no objects to be sliced, skipping", build_plate_to_be_sliced)
@@ -217,9 +236,6 @@ class CuraEngineBackend(QObject, Backend):
self.slice()
return
- self._stored_layer_data = []
- self._stored_optimized_layer_data[build_plate_to_be_sliced] = []
-
if Application.getInstance().getPrintInformation() and build_plate_to_be_sliced == active_build_plate:
Application.getInstance().getPrintInformation().setToZeroPrintInformation(build_plate_to_be_sliced)
@@ -289,6 +305,7 @@ class CuraEngineBackend(QObject, Backend):
if job.isCancelled() or job.getError() or job.getResult() == StartSliceJob.StartJobResult.Error:
self.backendStateChange.emit(BackendState.Error)
+ self.backendError.emit(job)
return
if job.getResult() == StartSliceJob.StartJobResult.MaterialIncompatible:
@@ -297,6 +314,7 @@ class CuraEngineBackend(QObject, Backend):
"Unable to slice with the current material as it is incompatible with the selected machine or configuration."), title = catalog.i18nc("@info:title", "Unable to slice"))
self._error_message.show()
self.backendStateChange.emit(BackendState.Error)
+ self.backendError.emit(job)
else:
self.backendStateChange.emit(BackendState.NotStarted)
return
@@ -325,6 +343,7 @@ class CuraEngineBackend(QObject, Backend):
title = catalog.i18nc("@info:title", "Unable to slice"))
self._error_message.show()
self.backendStateChange.emit(BackendState.Error)
+ self.backendError.emit(job)
else:
self.backendStateChange.emit(BackendState.NotStarted)
return
@@ -347,6 +366,7 @@ class CuraEngineBackend(QObject, Backend):
title = catalog.i18nc("@info:title", "Unable to slice"))
self._error_message.show()
self.backendStateChange.emit(BackendState.Error)
+ self.backendError.emit(job)
return
if job.getResult() == StartSliceJob.StartJobResult.BuildPlateError:
@@ -355,6 +375,7 @@ class CuraEngineBackend(QObject, Backend):
title = catalog.i18nc("@info:title", "Unable to slice"))
self._error_message.show()
self.backendStateChange.emit(BackendState.Error)
+ self.backendError.emit(job)
else:
self.backendStateChange.emit(BackendState.NotStarted)
@@ -364,6 +385,7 @@ class CuraEngineBackend(QObject, Backend):
title = catalog.i18nc("@info:title", "Unable to slice"))
self._error_message.show()
self.backendStateChange.emit(BackendState.Error)
+ self.backendError.emit(job)
else:
self.backendStateChange.emit(BackendState.NotStarted)
self._invokeSlice()
@@ -405,7 +427,7 @@ class CuraEngineBackend(QObject, Backend):
return False
## Return a dict with number of objects per build plate
- def _numObjects(self):
+ def _numObjectsPerBuildPlate(self):
num_objects = defaultdict(int)
for node in DepthFirstIterator(self._scene.getRoot()):
# Only count sliceable objects
@@ -432,7 +454,7 @@ class CuraEngineBackend(QObject, Backend):
source_build_plate_number = source.callDecoration("getBuildPlateNumber")
if source == self._scene.getRoot():
# we got the root node
- num_objects = self._numObjects()
+ num_objects = self._numObjectsPerBuildPlate()
for build_plate_number in list(self._last_num_objects.keys()) + list(num_objects.keys()):
if build_plate_number not in self._last_num_objects or num_objects[build_plate_number] != self._last_num_objects[build_plate_number]:
self._last_num_objects[build_plate_number] = num_objects[build_plate_number]
@@ -520,11 +542,9 @@ class CuraEngineBackend(QObject, Backend):
elif property == "validationState":
if self._use_timer:
- self._is_error_check_scheduled = True
self._change_timer.stop()
def _onStackErrorCheckFinished(self):
- self._is_error_check_scheduled = False
if not self._slicing and self._build_plates_to_be_sliced:
self.needsSlicing()
self._onChanged()
@@ -550,12 +570,15 @@ class CuraEngineBackend(QObject, Backend):
self.processingProgress.emit(message.amount)
self.backendStateChange.emit(BackendState.Processing)
- # testing
def _invokeSlice(self):
if self._use_timer:
# if the error check is scheduled, wait for the error check finish signal to trigger auto-slice,
# otherwise business as usual
- if self._is_error_check_scheduled:
+ if self._machine_error_checker is None:
+ self._change_timer.stop()
+ return
+
+ if self._machine_error_checker.needToWaitForResult:
self._change_timer.stop()
else:
self._change_timer.start()
@@ -582,7 +605,12 @@ class CuraEngineBackend(QObject, Backend):
# See if we need to process the sliced layers job.
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
- if self._layer_view_active and (self._process_layers_job is None or not self._process_layers_job.isRunning()) and active_build_plate == self._start_slice_job_build_plate:
+ if (
+ self._layer_view_active and
+ (self._process_layers_job is None or not self._process_layers_job.isRunning()) and
+ active_build_plate == self._start_slice_job_build_plate and
+ active_build_plate not in self._build_plates_to_be_sliced):
+
self._startProcessSlicedLayersJob(active_build_plate)
# self._onActiveViewChanged()
self._start_slice_job_build_plate = None
@@ -621,7 +649,11 @@ class CuraEngineBackend(QObject, Backend):
if self._use_timer:
# if the error check is scheduled, wait for the error check finish signal to trigger auto-slice,
# otherwise business as usual
- if self._is_error_check_scheduled:
+ if self._machine_error_checker is None:
+ self._change_timer.stop()
+ return
+
+ if self._machine_error_checker.needToWaitForResult:
self._change_timer.stop()
else:
self._change_timer.start()
@@ -707,7 +739,11 @@ class CuraEngineBackend(QObject, Backend):
# There is data and we're not slicing at the moment
# if we are slicing, there is no need to re-calculate the data as it will be invalid in a moment.
# TODO: what build plate I am slicing
- if active_build_plate in self._stored_optimized_layer_data and not self._slicing and not self._process_layers_job:
+ if (active_build_plate in self._stored_optimized_layer_data and
+ not self._slicing and
+ not self._process_layers_job and
+ active_build_plate not in self._build_plates_to_be_sliced):
+
self._startProcessSlicedLayersJob(active_build_plate)
else:
self._layer_view_active = False
@@ -773,3 +809,9 @@ class CuraEngineBackend(QObject, Backend):
def tickle(self):
if self._use_timer:
self._change_timer.start()
+
+ def _extruderChanged(self):
+ for build_plate_number in range(self._multi_build_plate_model.maxBuildPlate + 1):
+ if build_plate_number not in self._build_plates_to_be_sliced:
+ self._build_plates_to_be_sliced.append(build_plate_number)
+ self._invokeSlice()
diff --git a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py
index c1fc597d80..cbc097bb33 100644
--- a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py
+++ b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py
@@ -81,7 +81,8 @@ class ProcessSlicedLayersJob(Job):
Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged)
- new_node = CuraSceneNode()
+ # The no_setting_override is here because adding the SettingOverrideDecorator will trigger a reslice
+ new_node = CuraSceneNode(no_setting_override = True)
new_node.addDecorator(BuildPlateDecorator(self._build_plate_number))
# Force garbage collection.
diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py
index 8b7205f8b2..0297a34385 100644
--- a/plugins/CuraEngineBackend/StartSliceJob.py
+++ b/plugins/CuraEngineBackend/StartSliceJob.py
@@ -129,21 +129,19 @@ class StartSliceJob(Job):
self.setResult(StartJobResult.MaterialIncompatible)
return
- for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(stack.getId()):
+ for position, extruder_stack in stack.extruders.items():
material = extruder_stack.findContainer({"type": "material"})
+ if not extruder_stack.isEnabled:
+ continue
if material:
if material.getMetaDataEntry("compatible") == False:
self.setResult(StartJobResult.MaterialIncompatible)
return
- # Validate settings per selectable model
- if Application.getInstance().getObjectsModel().stacksHaveErrors():
- self.setResult(StartJobResult.ObjectSettingError)
- return
# Don't slice if there is a per object setting with an error value.
for node in DepthFirstIterator(self._scene.getRoot()):
- if node.isSelectable():
+ if not isinstance(node, CuraSceneNode) or not node.isSelectable():
continue
if self._checkStackForErrors(node.callDecoration("getStack")):
@@ -193,11 +191,15 @@ class StartSliceJob(Job):
if per_object_stack:
is_non_printing_mesh = any(per_object_stack.getProperty(key, "value") for key in NON_PRINTING_MESH_SETTINGS)
- if node.callDecoration("getBuildPlateNumber") == self._build_plate_number:
- if not getattr(node, "_outside_buildarea", False) or is_non_printing_mesh:
- temp_list.append(node)
- if not is_non_printing_mesh:
- has_printing_mesh = True
+ # Find a reason not to add the node
+ if node.callDecoration("getBuildPlateNumber") != self._build_plate_number:
+ continue
+ if getattr(node, "_outside_buildarea", False) and not is_non_printing_mesh:
+ continue
+
+ temp_list.append(node)
+ if not is_non_printing_mesh:
+ has_printing_mesh = True
Job.yieldThread()
@@ -209,10 +211,22 @@ class StartSliceJob(Job):
if temp_list:
object_groups.append(temp_list)
+ extruders_enabled = {position: stack.isEnabled for position, stack in Application.getInstance().getGlobalContainerStack().extruders.items()}
+ filtered_object_groups = []
+ for group in object_groups:
+ stack = Application.getInstance().getGlobalContainerStack()
+ skip_group = False
+ for node in group:
+ if not extruders_enabled[node.callDecoration("getActiveExtruderPosition")]:
+ skip_group = True
+ break
+ if not skip_group:
+ filtered_object_groups.append(group)
+
# There are cases when there is nothing to slice. This can happen due to one at a time slicing not being
# able to find a possible sequence or because there are no objects on the build plate (or they are outside
# the build volume)
- if not object_groups:
+ if not filtered_object_groups:
self.setResult(StartJobResult.NothingToSlice)
return
@@ -223,9 +237,9 @@ class StartSliceJob(Job):
for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(stack.getId()):
self._buildExtruderMessage(extruder_stack)
- for group in object_groups:
+ for group in filtered_object_groups:
group_message = self._slice_message.addRepeatedMessage("object_lists")
- if group[0].getParent().callDecoration("isGroup"):
+ if group[0].getParent() is not None and group[0].getParent().callDecoration("isGroup"):
self._handlePerObjectSettings(group[0].getParent(), group_message)
for object in group:
mesh_data = object.getMeshData()
@@ -273,9 +287,15 @@ class StartSliceJob(Job):
# \return A dictionary of replacement tokens to the values they should be
# replaced with.
def _buildReplacementTokens(self, stack) -> dict:
+ default_extruder_position = int(Application.getInstance().getMachineManager().defaultExtruderPosition)
result = {}
for key in stack.getAllKeys():
- result[key] = stack.getProperty(key, "value")
+ setting_type = stack.definition.getProperty(key, "type")
+ value = stack.getProperty(key, "value")
+ if setting_type == "extruder" and value == -1:
+ # replace with the default value
+ value = default_extruder_position
+ result[key] = value
Job.yieldThread()
result["print_bed_temperature"] = result["material_bed_temperature"] # Renamed settings.
@@ -381,11 +401,11 @@ class StartSliceJob(Job):
# limit_to_extruder property.
def _buildGlobalInheritsStackMessage(self, stack):
for key in stack.getAllKeys():
- extruder = int(round(float(stack.getProperty(key, "limit_to_extruder"))))
- if extruder >= 0: #Set to a specific extruder.
+ extruder_position = int(round(float(stack.getProperty(key, "limit_to_extruder"))))
+ if extruder_position >= 0: # Set to a specific extruder.
setting_extruder = self._slice_message.addRepeatedMessage("limit_to_extruder")
setting_extruder.name = key
- setting_extruder.extruder = extruder
+ setting_extruder.extruder = extruder_position
Job.yieldThread()
## Check if a node has per object settings and ensure that they are set correctly in the message
diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py
index fd6c4680e8..66ee43209f 100644
--- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py
+++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py
@@ -69,7 +69,7 @@ class FirmwareUpdateCheckerJob(Job):
# If we do this in a cool way, the download url should be available in the JSON file
if self._set_download_url_callback:
- self._set_download_url_callback("https://ultimaker.com/en/resources/20500-upgrade-firmware")
+ self._set_download_url_callback("https://ultimaker.com/en/resources/23129-updating-the-firmware?utm_source=cura&utm_medium=software&utm_campaign=hw-update")
message.actionTriggered.connect(self._callback)
message.show()
diff --git a/plugins/GCodeGzWriter/GCodeGzWriter.py b/plugins/GCodeGzWriter/GCodeGzWriter.py
new file mode 100644
index 0000000000..06fafb5995
--- /dev/null
+++ b/plugins/GCodeGzWriter/GCodeGzWriter.py
@@ -0,0 +1,41 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+import gzip
+from io import StringIO, BufferedIOBase #To write the g-code to a temporary buffer, and for typing.
+from typing import List
+
+from UM.Logger import Logger
+from UM.Mesh.MeshWriter import MeshWriter #The class we're extending/implementing.
+from UM.PluginRegistry import PluginRegistry
+from UM.Scene.SceneNode import SceneNode #For typing.
+
+## A file writer that writes gzipped g-code.
+#
+# If you're zipping g-code, you might as well use gzip!
+class GCodeGzWriter(MeshWriter):
+ ## Writes the gzipped g-code to a stream.
+ #
+ # Note that even though the function accepts a collection of nodes, the
+ # entire scene is always written to the file since it is not possible to
+ # separate the g-code for just specific nodes.
+ #
+ # \param stream The stream to write the gzipped g-code to.
+ # \param nodes This is ignored.
+ # \param mode Additional information on what type of stream to use. This
+ # must always be binary mode.
+ # \return Whether the write was successful.
+ def write(self, stream: BufferedIOBase, nodes: List[SceneNode], mode = MeshWriter.OutputMode.BinaryMode) -> bool:
+ if mode != MeshWriter.OutputMode.BinaryMode:
+ Logger.log("e", "GCodeGzWriter does not support text mode.")
+ return False
+
+ #Get the g-code from the g-code writer.
+ gcode_textio = StringIO() #We have to convert the g-code into bytes.
+ success = PluginRegistry.getInstance().getPluginObject("GCodeWriter").write(gcode_textio, None)
+ if not success: #Writing the g-code failed. Then I can also not write the gzipped g-code.
+ return False
+
+ result = gzip.compress(gcode_textio.getvalue().encode("utf-8"))
+ stream.write(result)
+ return True
diff --git a/plugins/GCodeGzWriter/__init__.py b/plugins/GCodeGzWriter/__init__.py
new file mode 100644
index 0000000000..c001467b3d
--- /dev/null
+++ b/plugins/GCodeGzWriter/__init__.py
@@ -0,0 +1,22 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from . import GCodeGzWriter
+
+from UM.i18n import i18nCatalog
+catalog = i18nCatalog("cura")
+
+def getMetaData():
+ return {
+ "mesh_writer": {
+ "output": [{
+ "extension": "gcode.gz",
+ "description": catalog.i18nc("@item:inlistbox", "Compressed G-code File"),
+ "mime_type": "application/gzip",
+ "mode": GCodeGzWriter.GCodeGzWriter.OutputMode.BinaryMode
+ }]
+ }
+ }
+
+def register(app):
+ return { "mesh_writer": GCodeGzWriter.GCodeGzWriter() }
diff --git a/plugins/GCodeGzWriter/plugin.json b/plugins/GCodeGzWriter/plugin.json
new file mode 100644
index 0000000000..9774e9a25c
--- /dev/null
+++ b/plugins/GCodeGzWriter/plugin.json
@@ -0,0 +1,8 @@
+{
+ "name": "Compressed G-code Writer",
+ "author": "Ultimaker B.V.",
+ "version": "1.0.0",
+ "description": "Writes g-code to a compressed archive.",
+ "api": 4,
+ "i18n-catalog": "cura"
+}
diff --git a/plugins/GCodeProfileReader/GCodeProfileReader.py b/plugins/GCodeProfileReader/GCodeProfileReader.py
index 2a10e01d43..d6bda85a48 100644
--- a/plugins/GCodeProfileReader/GCodeProfileReader.py
+++ b/plugins/GCodeProfileReader/GCodeProfileReader.py
@@ -9,7 +9,7 @@ from UM.Logger import Logger
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
-from cura.ProfileReader import ProfileReader
+from cura.ProfileReader import ProfileReader, NoProfileException
## A class that reads profile data from g-code files.
#
@@ -66,6 +66,11 @@ class GCodeProfileReader(ProfileReader):
return None
serialized = unescapeGcodeComment(serialized)
+ serialized = serialized.strip()
+
+ if not serialized:
+ Logger.log("i", "No custom profile to import from this g-code: %s", file_name)
+ raise NoProfileException()
# serialized data can be invalid JSON
try:
diff --git a/plugins/GCodeWriter/GCodeWriter.py b/plugins/GCodeWriter/GCodeWriter.py
index 1b3b7264a1..ccd881afdc 100644
--- a/plugins/GCodeWriter/GCodeWriter.py
+++ b/plugins/GCodeWriter/GCodeWriter.py
@@ -1,17 +1,17 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
+import re # For escaping characters in the settings.
+import json
+import copy
+
from UM.Mesh.MeshWriter import MeshWriter
from UM.Logger import Logger
from UM.Application import Application
from UM.Settings.InstanceContainer import InstanceContainer
-from UM.Util import parseBool
-from cura.Settings.ExtruderManager import ExtruderManager
+from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
-import re #For escaping characters in the settings.
-import json
-import copy
## Writes g-code to a file.
#
@@ -45,6 +45,8 @@ class GCodeWriter(MeshWriter):
def __init__(self):
super().__init__()
+ self._application = Application.getInstance()
+
## Writes the g-code for the entire scene to a stream.
#
# Note that even though the function accepts a collection of nodes, the
@@ -94,7 +96,6 @@ class GCodeWriter(MeshWriter):
return flat_container
-
## Serialises a container stack to prepare it for writing at the end of the
# g-code.
#
@@ -104,15 +105,20 @@ class GCodeWriter(MeshWriter):
# \param settings A container stack to serialise.
# \return A serialised string of the settings.
def _serialiseSettings(self, stack):
+ container_registry = self._application.getContainerRegistry()
+ quality_manager = self._application.getQualityManager()
+
prefix = ";SETTING_" + str(GCodeWriter.version) + " " # The prefix to put before each line.
prefix_length = len(prefix)
+ quality_type = stack.quality.getMetaDataEntry("quality_type")
container_with_profile = stack.qualityChanges
if container_with_profile.getId() == "empty_quality_changes":
- Logger.log("e", "No valid quality profile found, not writing settings to g-code!")
- return ""
+ # If the global quality changes is empty, create a new one
+ quality_name = container_registry.uniqueName(stack.quality.getName())
+ container_with_profile = quality_manager._createQualityChanges(quality_type, quality_name, stack, None)
- flat_global_container = self._createFlattenedContainerInstance(stack.getTop(), container_with_profile)
+ flat_global_container = self._createFlattenedContainerInstance(stack.userChanges, container_with_profile)
# If the quality changes is not set, we need to set type manually
if flat_global_container.getMetaDataEntry("type", None) is None:
flat_global_container.addMetaDataEntry("type", "quality_changes")
@@ -121,41 +127,47 @@ class GCodeWriter(MeshWriter):
if flat_global_container.getMetaDataEntry("quality_type", None) is None:
flat_global_container.addMetaDataEntry("quality_type", stack.quality.getMetaDataEntry("quality_type", "normal"))
- # Change the default defintion
- default_machine_definition = "fdmprinter"
- if parseBool(stack.getMetaDataEntry("has_machine_quality", "False")):
- default_machine_definition = stack.getMetaDataEntry("quality_definition")
- if not default_machine_definition:
- default_machine_definition = stack.definition.getId()
- flat_global_container.setMetaDataEntry("definition", default_machine_definition)
+ # Get the machine definition ID for quality profiles
+ machine_definition_id_for_quality = getMachineDefinitionIDForQualitySearch(stack.definition)
+ flat_global_container.setMetaDataEntry("definition", machine_definition_id_for_quality)
serialized = flat_global_container.serialize()
data = {"global_quality": serialized}
- for extruder in sorted(stack.extruders.values(), key = lambda k: k.getMetaDataEntry("position")):
+ all_setting_keys = set(flat_global_container.getAllKeys())
+ for extruder in sorted(stack.extruders.values(), key = lambda k: int(k.getMetaDataEntry("position"))):
extruder_quality = extruder.qualityChanges
if extruder_quality.getId() == "empty_quality_changes":
- Logger.log("w", "No extruder quality profile found, not writing quality for extruder %s to file!", extruder.getId())
- continue
- flat_extruder_quality = self._createFlattenedContainerInstance(extruder.getTop(), extruder_quality)
+ # Same story, if quality changes is empty, create a new one
+ quality_name = container_registry.uniqueName(stack.quality.getName())
+ extruder_quality = quality_manager._createQualityChanges(quality_type, quality_name, stack, None)
+
+ flat_extruder_quality = self._createFlattenedContainerInstance(extruder.userChanges, extruder_quality)
# If the quality changes is not set, we need to set type manually
if flat_extruder_quality.getMetaDataEntry("type", None) is None:
flat_extruder_quality.addMetaDataEntry("type", "quality_changes")
# Ensure that extruder is set. (Can happen if we have empty quality changes).
- if flat_extruder_quality.getMetaDataEntry("extruder", None) is None:
- flat_extruder_quality.addMetaDataEntry("extruder", extruder.getBottom().getId())
+ if flat_extruder_quality.getMetaDataEntry("position", None) is None:
+ flat_extruder_quality.addMetaDataEntry("position", extruder.getMetaDataEntry("position"))
# Ensure that quality_type is set. (Can happen if we have empty quality changes).
if flat_extruder_quality.getMetaDataEntry("quality_type", None) is None:
flat_extruder_quality.addMetaDataEntry("quality_type", extruder.quality.getMetaDataEntry("quality_type", "normal"))
- # Change the default defintion
- flat_extruder_quality.setMetaDataEntry("definition", default_machine_definition)
+ # Change the default definition
+ flat_extruder_quality.setMetaDataEntry("definition", machine_definition_id_for_quality)
extruder_serialized = flat_extruder_quality.serialize()
data.setdefault("extruder_quality", []).append(extruder_serialized)
+ all_setting_keys.update(set(flat_extruder_quality.getAllKeys()))
+
+ # Check if there is any profiles
+ if not all_setting_keys:
+ Logger.log("i", "No custom settings found, not writing settings to g-code.")
+ return ""
+
json_string = json.dumps(data)
# Escape characters that have a special meaning in g-code comments.
@@ -169,5 +181,5 @@ class GCodeWriter(MeshWriter):
# Lines have 80 characters, so the payload of each line is 80 - prefix.
for pos in range(0, len(escaped_string), 80 - prefix_length):
- result += prefix + escaped_string[pos : pos + 80 - prefix_length] + "\n"
+ result += prefix + escaped_string[pos: pos + 80 - prefix_length] + "\n"
return result
diff --git a/plugins/MachineSettingsAction/MachineSettingsAction.py b/plugins/MachineSettingsAction/MachineSettingsAction.py
index baa0639d3f..ded59bf934 100755
--- a/plugins/MachineSettingsAction/MachineSettingsAction.py
+++ b/plugins/MachineSettingsAction/MachineSettingsAction.py
@@ -2,20 +2,16 @@
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import pyqtProperty, pyqtSignal
+
+import UM.i18n
from UM.FlameProfiler import pyqtSlot
-
-from cura.MachineAction import MachineAction
-
from UM.Application import Application
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.ExtruderManager import ExtruderManager
+from cura.MachineAction import MachineAction
from cura.Settings.CuraStackBuilder import CuraStackBuilder
-import UM.i18n
catalog = UM.i18n.i18nCatalog("cura")
@@ -26,6 +22,8 @@ class MachineSettingsAction(MachineAction):
super().__init__("MachineSettingsAction", catalog.i18nc("@action", "Machine Settings"))
self._qml_url = "MachineSettingsAction.qml"
+ self._application = Application.getInstance()
+
self._global_container_stack = None
from cura.Settings.CuraContainerStack import _ContainerIndexes
@@ -34,38 +32,44 @@ class MachineSettingsAction(MachineAction):
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)
+ self._application.globalContainerStackChanged.connect(self._onGlobalContainerChanged)
- self._empty_container = self._container_registry.getEmptyInstanceContainer()
+ self._backend = self._application.getBackend()
- self._backend = Application.getInstance().getBackend()
+ self._empty_definition_container_id_list = []
+
+ def _isEmptyDefinitionChanges(self, container_id: str):
+ if not self._empty_definition_container_id_list:
+ self._empty_definition_container_id_list = [self._application.empty_container.getId(),
+ self._application.empty_definition_changes_container.getId()]
+ return container_id in self._empty_definition_container_id_list
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())
+ self._application.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.definitionChanges
- if definition_changes_container == self._empty_container:
+ definition_changes_id = container.definitionChanges.getId()
+ if self._isEmptyDefinitionChanges(definition_changes_id):
return
- self._container_registry.removeContainer(definition_changes_container.getId())
+ self._container_registry.removeContainer(definition_changes_id)
def _reset(self):
if not self._global_container_stack:
return
# Make sure there is a definition_changes container to store the machine settings
- definition_changes_container = self._global_container_stack.definitionChanges
- if definition_changes_container == self._empty_container:
- definition_changes_container = CuraStackBuilder.createDefinitionChangesContainer(
- self._global_container_stack, self._global_container_stack.getName() + "_settings")
+ definition_changes_id = self._global_container_stack.definitionChanges.getId()
+ if self._isEmptyDefinitionChanges(definition_changes_id):
+ CuraStackBuilder.createDefinitionChangesContainer(self._global_container_stack,
+ self._global_container_stack.getName() + "_settings")
# Notify the UI in which container to store the machine settings data
- from cura.Settings.CuraContainerStack import CuraContainerStack, _ContainerIndexes
+ from cura.Settings.CuraContainerStack import _ContainerIndexes
container_index = _ContainerIndexes.DefinitionChanges
if container_index != self._container_index:
@@ -107,13 +111,13 @@ class MachineSettingsAction(MachineAction):
def setMachineExtruderCount(self, extruder_count):
# Note: this method was in this class before, but since it's quite generic and other plugins also need it
# it was moved to the machine manager instead. Now this method just calls the machine manager.
- Application.getInstance().getMachineManager().setActiveMachineExtruderCount(extruder_count)
+ self._application.getMachineManager().setActiveMachineExtruderCount(extruder_count)
@pyqtSlot()
def forceUpdate(self):
# Force rebuilding the build volume by reloading the global container stack.
# This is a bit of a hack, but it seems quick enough.
- Application.getInstance().globalContainerStackChanged.emit()
+ self._application.globalContainerStackChanged.emit()
@pyqtSlot()
def updateHasMaterialsMetadata(self):
@@ -126,9 +130,11 @@ class MachineSettingsAction(MachineAction):
# In other words: only continue for the UM2 (extended), but not for the UM2+
return
- stacks = ExtruderManager.getInstance().getExtruderStacks()
+ machine_manager = self._application.getMachineManager()
+ extruder_positions = list(self._global_container_stack.extruders.keys())
has_materials = self._global_container_stack.getProperty("machine_gcode_flavor", "value") != "UltiGCode"
+ material_node = None
if has_materials:
if "has_materials" in self._global_container_stack.getMetaData():
self._global_container_stack.setMetaDataEntry("has_materials", True)
@@ -136,26 +142,22 @@ class MachineSettingsAction(MachineAction):
self._global_container_stack.addMetaDataEntry("has_materials", True)
# Set the material container for each extruder to a sane default
- for stack in stacks:
- material_container = stack.material
- if material_container == self._empty_container:
- machine_approximate_diameter = str(round(self._global_container_stack.getProperty("material_diameter", "value")))
- search_criteria = { "type": "material", "definition": "fdmprinter", "id": self._global_container_stack.getMetaDataEntry("preferred_material"), "approximate_diameter": machine_approximate_diameter}
- materials = self._container_registry.findInstanceContainers(**search_criteria)
- if materials:
- stack.material = materials[0]
+ material_manager = self._application.getMaterialManager()
+ material_node = material_manager.getDefaultMaterial(self._global_container_stack, None)
+
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")
- for stack in stacks:
- stack.material = ContainerRegistry.getInstance().getEmptyInstanceContainer()
+ # set materials
+ for position in extruder_positions:
+ machine_manager.setMaterial(position, material_node)
- Application.getInstance().globalContainerStackChanged.emit()
+ self._application.globalContainerStackChanged.emit()
@pyqtSlot(int)
def updateMaterialForDiameter(self, extruder_position: int):
# Updates the material container to a material that matches the material diameter set for the printer
- Application.getInstance().getExtruderManager().updateMaterialForDiameter(extruder_position)
+ self._application.getExtruderManager().updateMaterialForDiameter(extruder_position)
diff --git a/plugins/MachineSettingsAction/MachineSettingsAction.qml b/plugins/MachineSettingsAction/MachineSettingsAction.qml
index 4d00904f76..b12f8f8696 100644
--- a/plugins/MachineSettingsAction/MachineSettingsAction.qml
+++ b/plugins/MachineSettingsAction/MachineSettingsAction.qml
@@ -244,6 +244,7 @@ Cura.MachineAction
height: childrenRect.height
width: childrenRect.width
text: machineExtruderCountProvider.properties.description
+ visible: extruderCountModel.count >= 2
Row
{
@@ -381,6 +382,11 @@ Cura.MachineAction
property string settingKey: "machine_nozzle_size"
property string label: catalog.i18nc("@label", "Nozzle size")
property string unit: catalog.i18nc("@label", "mm")
+ function afterOnEditingFinished()
+ {
+ // Somehow the machine_nozzle_size dependent settings are not updated otherwise
+ Cura.MachineManager.forceUpdateAllSettings()
+ }
property bool isExtruderSetting: true
}
@@ -888,4 +894,4 @@ Cura.MachineAction
watchedProperties: [ "value" ]
storeIndex: manager.containerIndex
}
-}
\ No newline at end of file
+}
diff --git a/plugins/MonitorStage/MonitorStage.py b/plugins/MonitorStage/MonitorStage.py
index 1a1d37cbdf..931c205fff 100644
--- a/plugins/MonitorStage/MonitorStage.py
+++ b/plugins/MonitorStage/MonitorStage.py
@@ -22,14 +22,7 @@ class MonitorStage(CuraStage):
def _setActivePrintJob(self, print_job):
if self._active_print_job != print_job:
- if self._active_print_job:
- self._active_print_job.stateChanged.disconnect(self._updateIconSource)
self._active_print_job = print_job
- if self._active_print_job:
- self._active_print_job.stateChanged.connect(self._updateIconSource)
-
- # Ensure that the right icon source is returned.
- self._updateIconSource()
def _setActivePrinter(self, printer):
if self._active_printer != printer:
@@ -43,9 +36,6 @@ class MonitorStage(CuraStage):
else:
self._setActivePrintJob(None)
- # Ensure that the right icon source is returned.
- self._updateIconSource()
-
def _onActivePrintJobChanged(self):
self._setActivePrintJob(self._active_printer.activePrintJob)
@@ -58,22 +48,17 @@ class MonitorStage(CuraStage):
new_output_device = Application.getInstance().getMachineManager().printerOutputDevices[0]
if new_output_device != self._printer_output_device:
if self._printer_output_device:
- self._printer_output_device.acceptsCommandsChanged.disconnect(self._updateIconSource)
- self._printer_output_device.connectionStateChanged.disconnect(self._updateIconSource)
- self._printer_output_device.printersChanged.disconnect(self._onActivePrinterChanged)
+ try:
+ self._printer_output_device.printersChanged.disconnect(self._onActivePrinterChanged)
+ except TypeError:
+ # Ignore stupid "Not connected" errors.
+ pass
self._printer_output_device = new_output_device
- self._printer_output_device.acceptsCommandsChanged.connect(self._updateIconSource)
self._printer_output_device.printersChanged.connect(self._onActivePrinterChanged)
- self._printer_output_device.connectionStateChanged.connect(self._updateIconSource)
self._setActivePrinter(self._printer_output_device.activePrinter)
-
- # Force an update of the icon source
- self._updateIconSource()
except IndexError:
- #If index error occurs, then the icon on monitor button also should be updated
- self._updateIconSource()
pass
def _onEngineCreated(self):
@@ -82,7 +67,6 @@ class MonitorStage(CuraStage):
self._onOutputDevicesChanged()
self._updateMainOverlay()
self._updateSidebar()
- self._updateIconSource()
def _updateMainOverlay(self):
main_component_path = os.path.join(PluginRegistry.getInstance().getPluginPath("MonitorStage"), "MonitorMainView.qml")
@@ -92,46 +76,3 @@ class MonitorStage(CuraStage):
# TODO: currently the sidebar component for prepare and monitor stages is the same, this will change with the printer output device refactor!
sidebar_component_path = os.path.join(Resources.getPath(Application.getInstance().ResourceTypes.QmlFiles), "Sidebar.qml")
self.addDisplayComponent("sidebar", sidebar_component_path)
-
- def _updateIconSource(self):
- if Application.getInstance().getTheme() is not None:
- icon_name = self._getActiveOutputDeviceStatusIcon()
- self.setIconSource(Application.getInstance().getTheme().getIcon(icon_name))
-
- ## Find the correct status icon depending on the active output device state
- def _getActiveOutputDeviceStatusIcon(self):
- # We assume that you are monitoring the device with the highest priority.
- try:
- output_device = Application.getInstance().getMachineManager().printerOutputDevices[0]
- except IndexError:
- return "tab_status_unknown"
-
- if not output_device.acceptsCommands:
- return "tab_status_unknown"
-
- if output_device.activePrinter is None:
- return "tab_status_connected"
-
- # TODO: refactor to use enum instead of hardcoded strings?
- if output_device.activePrinter.state == "maintenance":
- return "tab_status_busy"
-
- if output_device.activePrinter.activePrintJob is None:
- return "tab_status_connected"
-
- if output_device.activePrinter.activePrintJob.state in ["printing", "pre_print", "pausing", "resuming"]:
- return "tab_status_busy"
-
- if output_device.activePrinter.activePrintJob.state == "wait_cleanup":
- return "tab_status_finished"
-
- if output_device.activePrinter.activePrintJob.state in ["ready", ""]:
- return "tab_status_connected"
-
- if output_device.activePrinter.activePrintJob.state == "paused":
- return "tab_status_paused"
-
- if output_device.activePrinter.activePrintJob.state == "error":
- return "tab_status_stopped"
-
- return "tab_status_unknown"
diff --git a/plugins/PerObjectSettingsTool/PerObjectItem.qml b/plugins/PerObjectSettingsTool/PerObjectItem.qml
index 1317c00b19..559ad2bf81 100644
--- a/plugins/PerObjectSettingsTool/PerObjectItem.qml
+++ b/plugins/PerObjectSettingsTool/PerObjectItem.qml
@@ -25,20 +25,7 @@ UM.TooltipArea
onClicked:
{
- // Important first set visible and then subscribe
- // otherwise the setting is not yet in list
- // For unsubscribe is important first remove the subscription and then
- // set as invisible
- if(checked)
- {
- addedSettingsModel.setVisible(model.key, checked);
- UM.ActiveTool.triggerActionWithData("subscribeForSettingValidation", model.key)
- }
- else
- {
- UM.ActiveTool.triggerActionWithData("unsubscribeForSettingValidation", model.key)
- addedSettingsModel.setVisible(model.key, checked);
- }
+ addedSettingsModel.setVisible(model.key, checked);
UM.ActiveTool.forceUpdate();
}
}
diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml
index e72e1224df..a2790dcf08 100644
--- a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml
+++ b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml
@@ -163,7 +163,16 @@ Item {
id: addedSettingsModel;
containerId: Cura.MachineManager.activeDefinitionId
expanded: [ "*" ]
- exclude: {
+ filter:
+ {
+ if (printSequencePropertyProvider.properties.value == "one_at_a_time")
+ {
+ return {"settable_per_meshgroup": true};
+ }
+ return {"settable_per_mesh": true};
+ }
+ exclude:
+ {
var excluded_settings = [ "support_mesh", "anti_overhang_mesh", "cutting_mesh", "infill_mesh" ];
if(meshTypeSelection.model.get(meshTypeSelection.currentIndex).type == "support_mesh")
@@ -240,10 +249,7 @@ Item {
width: Math.round(UM.Theme.getSize("setting").height / 2)
height: UM.Theme.getSize("setting").height
- onClicked: {
- addedSettingsModel.setVisible(model.key, false)
- UM.ActiveTool.triggerActionWithData("unsubscribeForSettingValidation", model.key)
- }
+ onClicked: addedSettingsModel.setVisible(model.key, false)
style: ButtonStyle
{
@@ -378,7 +384,6 @@ Item {
title: catalog.i18nc("@title:window", "Select Settings to Customize for this model")
width: screenScaleFactor * 360
- property string labelFilter: ""
property var additional_excluded_settings
onVisibilityChanged:
@@ -389,11 +394,33 @@ Item {
// Set skip setting, it will prevent from resetting selected mesh_type
contents.model.visibilityHandler.addSkipResetSetting(meshTypeSelection.model.get(meshTypeSelection.currentIndex).type)
listview.model.forceUpdate()
+
+ updateFilter()
}
}
+ function updateFilter()
+ {
+ var new_filter = {};
+ if (printSequencePropertyProvider.properties.value == "one_at_a_time")
+ {
+ new_filter["settable_per_meshgroup"] = true;
+ }
+ else
+ {
+ new_filter["settable_per_mesh"] = true;
+ }
+
+ if(filterInput.text != "")
+ {
+ new_filter["i18n_label"] = "*" + filterInput.text;
+ }
+
+ listview.model.filter = new_filter;
+ }
+
TextField {
- id: filter
+ id: filterInput
anchors {
top: parent.top
@@ -404,17 +431,7 @@ Item {
placeholderText: catalog.i18nc("@label:textbox", "Filter...");
- onTextChanged:
- {
- if(text != "")
- {
- listview.model.filter = {"settable_per_mesh": true, "i18n_label": "*" + text}
- }
- else
- {
- listview.model.filter = {"settable_per_mesh": true}
- }
- }
+ onTextChanged: settingPickDialog.updateFilter()
}
CheckBox
@@ -440,7 +457,7 @@ Item {
anchors
{
- top: filter.bottom;
+ top: filterInput.bottom;
left: parent.left;
right: parent.right;
bottom: parent.bottom;
@@ -452,10 +469,6 @@ Item {
{
id: definitionsModel;
containerId: Cura.MachineManager.activeDefinitionId
- filter:
- {
- "settable_per_mesh": true
- }
visibilityHandler: UM.SettingPreferenceVisibilityHandler {}
expanded: [ "*" ]
exclude:
@@ -487,6 +500,7 @@ Item {
}
}
}
+ Component.onCompleted: settingPickDialog.updateFilter()
}
}
@@ -510,6 +524,16 @@ Item {
storeIndex: 0
}
+ UM.SettingPropertyProvider
+ {
+ id: printSequencePropertyProvider
+
+ containerStackId: Cura.MachineManager.activeMachineId
+ key: "print_sequence"
+ watchedProperties: [ "value" ]
+ storeIndex: 0
+ }
+
SystemPalette { id: palette; }
Component
diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py b/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py
index b671db48fb..d2db5ff420 100644
--- a/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py
+++ b/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py
@@ -10,10 +10,7 @@ from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
from cura.Settings.ExtruderManager import ExtruderManager
from UM.Settings.SettingInstance import SettingInstance
from UM.Event import Event
-from UM.Settings.Validator import ValidatorState
-from UM.Logger import Logger
-from PyQt5.QtCore import QTimer
## This tool allows the user to add & change settings per node in the scene.
# The settings per object are kept in a ContainerStack, which is linked to a node by decorator.
@@ -37,12 +34,6 @@ class PerObjectSettingsTool(Tool):
self._onGlobalContainerChanged()
Selection.selectionChanged.connect(self._updateEnabled)
- self._scene = Application.getInstance().getController().getScene()
-
- 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)
def event(self, event):
super().event(event)
@@ -151,65 +142,3 @@ class PerObjectSettingsTool(Tool):
else:
self._single_model_selected = True
Application.getInstance().getController().toolEnabledChanged.emit(self._plugin_id, self._advanced_mode and self._single_model_selected)
-
-
- def _onPropertyChanged(self, key: str, property_name: str) -> None:
- if property_name == "validationState":
- # self._error_check_timer.start()
- return
-
- def _updateStacksHaveErrors(self) -> None:
- return
- # self._checkStacksHaveErrors()
-
-
- def _checkStacksHaveErrors(self):
-
- for node in DepthFirstIterator(self._scene.getRoot()):
-
- # valdiate only objects which can be selected because the settings per object
- # can be applied only for them
- if not node.isSelectable():
- continue
-
- hasErrors = self._checkStackForErrors(node.callDecoration("getStack"))
- Application.getInstance().getObjectsModel().setStacksHaveErrors(hasErrors)
-
- #If any of models has an error then no reason check next objects on the build plate
- if hasErrors:
- break
-
-
- def _checkStackForErrors(self, stack):
- print("checking for errors")
- if stack is None:
- return False
-
- for key in stack.getAllKeys():
- validation_state = stack.getProperty(key, "validationState")
- if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError):
- Logger.log("w", "Setting Per Object %s is not valid.", key)
- return True
- return False
-
- def subscribeForSettingValidation(self, setting_name):
- selected_object = Selection.getSelectedObject(0)
- stack = selected_object.callDecoration("getStack") # Don't try to get the active extruder since it may be None anyway.
- if not stack:
- return ""
-
- settings = stack.getTop()
- setting_instance = settings.getInstance(setting_name)
- if setting_instance:
- setting_instance.propertyChanged.connect(self._onPropertyChanged)
-
- def unsubscribeForSettingValidation(self, setting_name):
- selected_object = Selection.getSelectedObject(0)
- stack = selected_object.callDecoration("getStack") # Don't try to get the active extruder since it may be None anyway.
- if not stack:
- return ""
-
- settings = stack.getTop()
- setting_instance = settings.getInstance(setting_name)
- if setting_instance:
- setting_instance.propertyChanged.disconnect(self._onPropertyChanged)
diff --git a/plugins/PostProcessingPlugin/PostProcessingPlugin.py b/plugins/PostProcessingPlugin/PostProcessingPlugin.py
index 566b05abf3..c4b760724b 100644
--- a/plugins/PostProcessingPlugin/PostProcessingPlugin.py
+++ b/plugins/PostProcessingPlugin/PostProcessingPlugin.py
@@ -173,7 +173,10 @@ class PostProcessingPlugin(QObject, Extension):
Logger.log("d", "Creating post processing plugin view.")
## Load all scripts in the scripts folders
- for root in [PluginRegistry.getInstance().getPluginPath("PostProcessingPlugin"), Resources.getStoragePath(Resources.Preferences)]:
+ # The PostProcessingPlugin path is for built-in scripts.
+ # The Resources path is where the user should store custom scripts.
+ # The Preferences path is legacy, where the user may previously have stored scripts.
+ for root in [PluginRegistry.getInstance().getPluginPath("PostProcessingPlugin"), Resources.getStoragePath(Resources.Resources), Resources.getStoragePath(Resources.Preferences)]:
path = os.path.join(root, "scripts")
if not os.path.isdir(path):
try:
diff --git a/plugins/PostProcessingPlugin/scripts/ColorChange.py b/plugins/PostProcessingPlugin/scripts/FilamentChange.py
similarity index 75%
rename from plugins/PostProcessingPlugin/scripts/ColorChange.py
rename to plugins/PostProcessingPlugin/scripts/FilamentChange.py
index 8db45f4033..07e887b082 100644
--- a/plugins/PostProcessingPlugin/scripts/ColorChange.py
+++ b/plugins/PostProcessingPlugin/scripts/FilamentChange.py
@@ -2,17 +2,15 @@
# under the terms of the AGPLv3 or higher
from ..Script import Script
-#from UM.Logger import Logger
-# from cura.Settings.ExtruderManager import ExtruderManager
-class ColorChange(Script):
+class FilamentChange(Script):
def __init__(self):
super().__init__()
def getSettingDataString(self):
return """{
- "name":"Color Change",
- "key": "ColorChange",
+ "name":"Filament Change",
+ "key": "FilamentChange",
"metadata": {},
"version": 2,
"settings":
@@ -29,18 +27,18 @@ class ColorChange(Script):
"initial_retract":
{
"label": "Initial Retraction",
- "description": "Initial filament retraction distance",
+ "description": "Initial filament retraction distance. The filament will be retracted with this amount before moving the nozzle away from the ongoing print.",
"unit": "mm",
"type": "float",
- "default_value": 300.0
+ "default_value": 30.0
},
"later_retract":
{
"label": "Later Retraction Distance",
- "description": "Later filament retraction distance for removal",
+ "description": "Later filament retraction distance for removal. The filament will be retracted all the way out of the printer so that you can change the filament.",
"unit": "mm",
"type": "float",
- "default_value": 30.0
+ "default_value": 300.0
}
}
}"""
@@ -60,17 +58,17 @@ class ColorChange(Script):
if later_retract is not None and later_retract > 0.:
color_change = color_change + (" L%.2f" % later_retract)
- color_change = color_change + " ; Generated by ColorChange plugin"
+ color_change = color_change + " ; Generated by FilamentChange plugin"
- layer_targets = layer_nums.split(',')
+ layer_targets = layer_nums.split(",")
if len(layer_targets) > 0:
for layer_num in layer_targets:
- layer_num = int( layer_num.strip() )
+ layer_num = int(layer_num.strip())
if layer_num < len(data):
- layer = data[ layer_num - 1 ]
+ layer = data[layer_num - 1]
lines = layer.split("\n")
- lines.insert(2, color_change )
- final_line = "\n".join( lines )
- data[ layer_num - 1 ] = final_line
+ lines.insert(2, color_change)
+ final_line = "\n".join(lines)
+ data[layer_num - 1] = final_line
return data
diff --git a/plugins/PostProcessingPlugin/scripts/Stretch.py b/plugins/PostProcessingPlugin/scripts/Stretch.py
index bcb923d3ff..c7a36ab7d6 100644
--- a/plugins/PostProcessingPlugin/scripts/Stretch.py
+++ b/plugins/PostProcessingPlugin/scripts/Stretch.py
@@ -12,6 +12,7 @@ import numpy as np
from UM.Logger import Logger
from UM.Application import Application
import re
+from cura.Settings.ExtruderManager import ExtruderManager
def _getValue(line, key, default=None):
"""
@@ -90,9 +91,9 @@ class Stretcher():
"""
Computes the new X and Y coordinates of all g-code steps
"""
- Logger.log("d", "Post stretch with line width = " + str(self.line_width)
- + "mm wide circle stretch = " + str(self.wc_stretch)+ "mm"
- + "and push wall stretch = " + str(self.pw_stretch) + "mm")
+ Logger.log("d", "Post stretch with line width " + str(self.line_width)
+ + "mm wide circle stretch " + str(self.wc_stretch)+ "mm"
+ + " and push wall stretch " + str(self.pw_stretch) + "mm")
retdata = []
layer_steps = []
current = GCodeStep(0)
@@ -282,7 +283,7 @@ class Stretcher():
dmin_tri is the minimum distance between two consecutive points
of an acceptable triangle
"""
- dmin_tri = self.line_width / 2.0
+ dmin_tri = 0.5
iextra_base = np.floor_divide(len(orig_seq), 3) # Nb of extra points
ibeg = 0 # Index of first point of the triangle
iend = 0 # Index of the third point of the triangle
@@ -325,9 +326,10 @@ class Stretcher():
relpos = 0.5 # To avoid division by zero or precision loss
projection = (pos_before[ibeg] + relpos * (pos_after[iend] - pos_before[ibeg]))
dist_from_proj = np.sqrt(((projection - step) ** 2).sum(0))
- if dist_from_proj > 0.001: # Move central point only if points are not aligned
+ if dist_from_proj > 0.0003: # Move central point only if points are not aligned
modif_seq[i] = (step - (self.wc_stretch / dist_from_proj)
* (projection - step))
+
return
def wideTurn(self, orig_seq, modif_seq):
@@ -411,8 +413,6 @@ class Stretcher():
modif_seq[ibeg] = modif_seq[ibeg] + xperp * self.pw_stretch
elif not materialleft and materialright:
modif_seq[ibeg] = modif_seq[ibeg] - xperp * self.pw_stretch
- if materialleft and materialright:
- modif_seq[ibeg] = orig_seq[ibeg] # Surrounded by walls, don't move
# Setup part of the stretch plugin
class Stretch(Script):
@@ -437,7 +437,7 @@ class Stretch(Script):
"description": "Distance by which the points are moved by the correction effect in corners. The higher this value, the higher the effect",
"unit": "mm",
"type": "float",
- "default_value": 0.08,
+ "default_value": 0.1,
"minimum_value": 0,
"minimum_value_warning": 0,
"maximum_value_warning": 0.2
@@ -448,7 +448,7 @@ class Stretch(Script):
"description": "Distance by which the points are moved by the correction effect when two lines are nearby. The higher this value, the higher the effect",
"unit": "mm",
"type": "float",
- "default_value": 0.08,
+ "default_value": 0.1,
"minimum_value": 0,
"minimum_value_warning": 0,
"maximum_value_warning": 0.2
@@ -463,7 +463,7 @@ class Stretch(Script):
the returned string is the list of modified g-code instructions
"""
stretcher = Stretcher(
- Application.getInstance().getGlobalContainerStack().getProperty("line_width", "value")
+ ExtruderManager.getInstance().getActiveExtruderStack().getProperty("machine_nozzle_size", "value")
, self.getSettingValueByKey("wc_stretch"), self.getSettingValueByKey("pw_stretch"))
return stretcher.execute(data)
diff --git a/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py b/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py
index ff930e2c31..c81e4a76bc 100644
--- a/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py
+++ b/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2016 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import os.path
@@ -7,7 +7,7 @@ from UM.Application import Application
from UM.Logger import Logger
from UM.Message import Message
from UM.FileHandler.WriteFileJob import WriteFileJob
-from UM.Mesh.MeshWriter import MeshWriter
+from UM.FileHandler.FileWriter import FileWriter #To check against the write modes (text vs. binary).
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
from UM.OutputDevice.OutputDevice import OutputDevice
from UM.OutputDevice import OutputDeviceError
@@ -39,7 +39,7 @@ class RemovableDriveOutputDevice(OutputDevice):
# MIME types available to the currently active machine?
#
def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None, **kwargs):
- filter_by_machine = True # This plugin is indended to be used by machine (regardless of what it was told to do)
+ filter_by_machine = True # This plugin is intended to be used by machine (regardless of what it was told to do)
if self._writing:
raise OutputDeviceError.DeviceBusyError()
@@ -56,19 +56,21 @@ class RemovableDriveOutputDevice(OutputDevice):
machine_file_formats = [file_type.strip() for file_type in container.getMetaDataEntry("file_formats").split(";")]
# Take the intersection between file_formats and machine_file_formats.
- file_formats = list(filter(lambda file_format: file_format["mime_type"] in machine_file_formats, file_formats))
+ format_by_mimetype = {format["mime_type"]: format for format in file_formats}
+ file_formats = [format_by_mimetype[mimetype] for mimetype in machine_file_formats] #Keep them ordered according to the preference in machine_file_formats.
if len(file_formats) == 0:
Logger.log("e", "There are no file formats available to write with!")
- raise OutputDeviceError.WriteRequestFailedError(catalog.i18nc("There are no file formats available to write with!"))
+ raise OutputDeviceError.WriteRequestFailedError(catalog.i18nc("@info:status", "There are no file formats available to write with!"))
+ preferred_format = file_formats[0]
# Just take the first file format available.
if file_handler is not None:
- writer = file_handler.getWriterByMimeType(file_formats[0]["mime_type"])
+ writer = file_handler.getWriterByMimeType(preferred_format["mime_type"])
else:
- writer = Application.getInstance().getMeshFileHandler().getWriterByMimeType(file_formats[0]["mime_type"])
+ writer = Application.getInstance().getMeshFileHandler().getWriterByMimeType(preferred_format["mime_type"])
- extension = file_formats[0]["extension"]
+ extension = preferred_format["extension"]
if file_name is None:
file_name = self._automaticFileName(nodes)
@@ -80,8 +82,11 @@ class RemovableDriveOutputDevice(OutputDevice):
try:
Logger.log("d", "Writing to %s", file_name)
# Using buffering greatly reduces the write time for many lines of gcode
- self._stream = open(file_name, "wt", buffering = 1, encoding = "utf-8")
- job = WriteFileJob(writer, self._stream, nodes, MeshWriter.OutputMode.TextMode)
+ if preferred_format["mode"] == FileWriter.OutputMode.TextMode:
+ self._stream = open(file_name, "wt", buffering = 1, encoding = "utf-8")
+ else: #Binary mode.
+ self._stream = open(file_name, "wb", buffering = 1)
+ job = WriteFileJob(writer, self._stream, nodes, preferred_format["mode"])
job.setFileName(file_name)
job.progress.connect(self._onProgress)
job.finished.connect(self._onFinished)
diff --git a/plugins/SimulationView/SimulationView.py b/plugins/SimulationView/SimulationView.py
index 35ce9cc37a..3697e38661 100644
--- a/plugins/SimulationView/SimulationView.py
+++ b/plugins/SimulationView/SimulationView.py
@@ -74,7 +74,7 @@ class SimulationView(View):
self._global_container_stack = None
self._proxy = SimulationViewProxy()
- self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged)
+ self._controller.getScene().sceneChanged.connect(self._onSceneChanged)
self._resetSettings()
self._legend_items = None
@@ -158,9 +158,12 @@ class SimulationView(View):
return self._nozzle_node
def _onSceneChanged(self, node):
- self.setActivity(False)
- self.calculateMaxLayers()
- self.calculateMaxPathsOnLayer(self._current_layer_num)
+ if node.getMeshData() is None:
+ self.resetLayerData()
+ else:
+ self.setActivity(False)
+ self.calculateMaxLayers()
+ self.calculateMaxPathsOnLayer(self._current_layer_num)
def isBusy(self):
return self._busy
diff --git a/plugins/SliceInfoPlugin/SliceInfo.py b/plugins/SliceInfoPlugin/SliceInfo.py
index 971a324aa2..e1c990c596 100755
--- a/plugins/SliceInfoPlugin/SliceInfo.py
+++ b/plugins/SliceInfoPlugin/SliceInfo.py
@@ -146,7 +146,7 @@ class SliceInfo(Extension):
model_stack = node.callDecoration("getStack")
if model_stack:
model_settings["support_enabled"] = model_stack.getProperty("support_enable", "value")
- model_settings["support_extruder_nr"] = int(model_stack.getProperty("support_extruder_nr", "value"))
+ model_settings["support_extruder_nr"] = int(model_stack.getExtruderPositionValueWithDefault("support_extruder_nr"))
# Mesh modifiers;
model_settings["infill_mesh"] = model_stack.getProperty("infill_mesh", "value")
@@ -177,7 +177,7 @@ class SliceInfo(Extension):
# Support settings
print_settings["support_enabled"] = global_container_stack.getProperty("support_enable", "value")
- print_settings["support_extruder_nr"] = int(global_container_stack.getProperty("support_extruder_nr", "value"))
+ print_settings["support_extruder_nr"] = int(global_container_stack.getExtruderPositionValueWithDefault("support_extruder_nr"))
# Platform adhesion settings
print_settings["adhesion_type"] = global_container_stack.getProperty("adhesion_type", "value")
diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py
index 50ff2864b7..ff5aab96d2 100644
--- a/plugins/SolidView/SolidView.py
+++ b/plugins/SolidView/SolidView.py
@@ -62,7 +62,7 @@ class SolidView(View):
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack:
- support_extruder_nr = global_container_stack.getProperty("support_extruder_nr", "value")
+ support_extruder_nr = global_container_stack.getExtruderPositionValueWithDefault("support_extruder_nr")
support_angle_stack = Application.getInstance().getExtruderManager().getExtruderStack(support_extruder_nr)
if support_angle_stack is not None and Preferences.getInstance().getValue("view/show_overhang"):
@@ -78,22 +78,18 @@ class SolidView(View):
for node in DepthFirstIterator(scene.getRoot()):
if not node.render(renderer):
- if node.getMeshData() and node.isVisible():
+ if node.getMeshData() and node.isVisible() and not node.callDecoration("getLayerData"):
uniforms = {}
shade_factor = 1.0
per_mesh_stack = node.callDecoration("getStack")
- # Get color to render this mesh in from ExtrudersModel
- extruder_index = 0
- extruder_id = node.callDecoration("getActiveExtruder")
- if extruder_id:
- extruder_index = max(0, self._extruders_model.find("id", extruder_id))
+ extruder_index = int(node.callDecoration("getActiveExtruderPosition"))
# Use the support extruder instead of the active extruder if this is a support_mesh
if per_mesh_stack:
if per_mesh_stack.getProperty("support_mesh", "value"):
- extruder_index = int(global_container_stack.getProperty("support_extruder_nr", "value"))
+ extruder_index = int(global_container_stack.getExtruderPositionValueWithDefault("support_extruder_nr"))
try:
material_color = self._extruders_model.getItem(extruder_index)["color"]
diff --git a/plugins/SupportEraser/SupportEraser.py b/plugins/SupportEraser/SupportEraser.py
index 8b3ad0f4dd..7884ca30c7 100644
--- a/plugins/SupportEraser/SupportEraser.py
+++ b/plugins/SupportEraser/SupportEraser.py
@@ -1,39 +1,101 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from UM.Math.Vector import Vector
-from UM.Tool import Tool
-from PyQt5.QtCore import Qt, QUrl
-from UM.Application import Application
-from UM.Event import Event
-from UM.Mesh.MeshBuilder import MeshBuilder
-from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
-from UM.Settings.SettingInstance import SettingInstance
-from cura.Scene.CuraSceneNode import CuraSceneNode
-from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
-from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
-from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
import os
import os.path
+from PyQt5.QtCore import Qt, QTimer
+from PyQt5.QtWidgets import QApplication
+
+from UM.Math.Vector import Vector
+from UM.Tool import Tool
+from UM.Application import Application
+from UM.Event import Event, MouseEvent
+
+from UM.Mesh.MeshBuilder import MeshBuilder
+from UM.Scene.Selection import Selection
+from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
+from cura.Scene.CuraSceneNode import CuraSceneNode
+
+from cura.PickingPass import PickingPass
+
+from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
+from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
+
+from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
+from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
+from UM.Scene.GroupDecorator import GroupDecorator
+from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
+
+from UM.Settings.SettingInstance import SettingInstance
+
class SupportEraser(Tool):
def __init__(self):
super().__init__()
self._shortcut_key = Qt.Key_G
- self._controller = Application.getInstance().getController()
+ self._controller = self.getController()
+
+ self._selection_pass = None
+ Application.getInstance().globalContainerStackChanged.connect(self._updateEnabled)
+
+ # Note: if the selection is cleared with this tool active, there is no way to switch to
+ # another tool than to reselect an object (by clicking it) because the tool buttons in the
+ # toolbar will have been disabled. That is why we need to ignore the first press event
+ # after the selection has been cleared.
+ Selection.selectionChanged.connect(self._onSelectionChanged)
+ self._had_selection = False
+ self._skip_press = False
+
+ self._had_selection_timer = QTimer()
+ self._had_selection_timer.setInterval(0)
+ self._had_selection_timer.setSingleShot(True)
+ self._had_selection_timer.timeout.connect(self._selectionChangeDelay)
def event(self, event):
super().event(event)
+ modifiers = QApplication.keyboardModifiers()
+ ctrl_is_active = modifiers & Qt.ControlModifier
- if event.type == Event.ToolActivateEvent:
+ if event.type == Event.MousePressEvent and self._controller.getToolsEnabled():
+ if ctrl_is_active:
+ self._controller.setActiveTool("TranslateTool")
+ return
- # Load the remover mesh:
- self._createEraserMesh()
+ if self._skip_press:
+ # The selection was previously cleared, do not add/remove an anti-support mesh but
+ # use this click for selection and reactivating this tool only.
+ self._skip_press = False
+ return
- # After we load the mesh, deactivate the tool again:
- self.getController().setActiveTool(None)
+ if self._selection_pass is None:
+ # The selection renderpass is used to identify objects in the current view
+ self._selection_pass = Application.getInstance().getRenderer().getRenderPass("selection")
+ picked_node = self._controller.getScene().findObject(self._selection_pass.getIdAtPosition(event.x, event.y))
+ if not picked_node:
+ # There is no slicable object at the picked location
+ return
- def _createEraserMesh(self):
+ node_stack = picked_node.callDecoration("getStack")
+ if node_stack:
+ if node_stack.getProperty("anti_overhang_mesh", "value"):
+ self._removeEraserMesh(picked_node)
+ return
+
+ elif node_stack.getProperty("support_mesh", "value") or node_stack.getProperty("infill_mesh", "value") or node_stack.getProperty("cutting_mesh", "value"):
+ # Only "normal" meshes can have anti_overhang_meshes added to them
+ return
+
+ # Create a pass for picking a world-space location from the mouse location
+ active_camera = self._controller.getScene().getActiveCamera()
+ picking_pass = PickingPass(active_camera.getViewportWidth(), active_camera.getViewportHeight())
+ picking_pass.render()
+
+ picked_position = picking_pass.getPickedPosition(event.x, event.y)
+
+ # Add the anti_overhang_mesh cube at the picked location
+ self._createEraserMesh(picked_node, picked_position)
+
+ def _createEraserMesh(self, parent: CuraSceneNode, position: Vector):
node = CuraSceneNode()
node.setName("Eraser")
@@ -41,31 +103,61 @@ class SupportEraser(Tool):
mesh = MeshBuilder()
mesh.addCube(10,10,10)
node.setMeshData(mesh.build())
- # Place the cube in the platform. Do it manually so it works if the "automatic drop models" is OFF
- move_vector = Vector(0, 5, 0)
- node.setPosition(move_vector)
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
-
- node.addDecorator(SettingOverrideDecorator())
node.addDecorator(BuildPlateDecorator(active_build_plate))
node.addDecorator(SliceableObjectDecorator())
- stack = node.callDecoration("getStack") #Don't try to get the active extruder since it may be None anyway.
- if not stack:
- node.addDecorator(SettingOverrideDecorator())
- stack = node.callDecoration("getStack")
-
+ stack = node.callDecoration("getStack") # created by SettingOverrideDecorator that is automatically added to CuraSceneNode
settings = stack.getTop()
- if not (settings.getInstance("anti_overhang_mesh") and settings.getProperty("anti_overhang_mesh", "value")):
- definition = stack.getSettingDefinition("anti_overhang_mesh")
- new_instance = SettingInstance(definition, settings)
- new_instance.setProperty("value", True)
- new_instance.resetState() # Ensure that the state is not seen as a user state.
- settings.addInstance(new_instance)
+ definition = stack.getSettingDefinition("anti_overhang_mesh")
+ new_instance = SettingInstance(definition, settings)
+ new_instance.setProperty("value", True)
+ new_instance.resetState() # Ensure that the state is not seen as a user state.
+ settings.addInstance(new_instance)
- scene = self._controller.getScene()
- op = AddSceneNodeOperation(node, scene.getRoot())
+ op = AddSceneNodeOperation(node, parent)
op.push()
+ node.setPosition(position, CuraSceneNode.TransformSpace.World)
+
Application.getInstance().getController().getScene().sceneChanged.emit(node)
+
+ def _removeEraserMesh(self, node: CuraSceneNode):
+ parent = node.getParent()
+ if parent == self._controller.getScene().getRoot():
+ parent = None
+
+ op = RemoveSceneNodeOperation(node)
+ op.push()
+
+ if parent and not Selection.isSelected(parent):
+ Selection.add(parent)
+
+ Application.getInstance().getController().getScene().sceneChanged.emit(node)
+
+ def _updateEnabled(self):
+ plugin_enabled = False
+
+ global_container_stack = Application.getInstance().getGlobalContainerStack()
+ if global_container_stack:
+ plugin_enabled = global_container_stack.getProperty("anti_overhang_mesh", "enabled")
+
+ Application.getInstance().getController().toolEnabledChanged.emit(self._plugin_id, plugin_enabled)
+
+ def _onSelectionChanged(self):
+ # When selection is passed from one object to another object, first the selection is cleared
+ # and then it is set to the new object. We are only interested in the change from no selection
+ # to a selection or vice-versa, not in a change from one object to another. A timer is used to
+ # "merge" a possible clear/select action in a single frame
+ if Selection.hasSelection() != self._had_selection:
+ self._had_selection_timer.start()
+
+ def _selectionChangeDelay(self):
+ has_selection = Selection.hasSelection()
+ if not has_selection and self._had_selection:
+ self._skip_press = True
+ else:
+ self._skip_press = False
+
+ self._had_selection = has_selection
diff --git a/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py b/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py
index c0d538bb78..72f5260249 100644
--- a/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py
+++ b/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py
@@ -1,12 +1,17 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
+from UM.FileHandler.FileWriter import FileWriter #To choose based on the output file mode (text vs. binary).
+from UM.FileHandler.WriteFileJob import WriteFileJob #To call the file writer asynchronously.
from UM.Logger import Logger
from UM.Application import Application
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.i18n import i18nCatalog
from UM.Message import Message
from UM.Qt.Duration import Duration, DurationFormat
+from UM.OutputDevice import OutputDeviceError #To show that something went wrong when writing.
+from UM.Scene.SceneNode import SceneNode #For typing.
+from UM.Version import Version #To check against firmware versions for support.
from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
@@ -20,10 +25,11 @@ from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal, pyqtProperty, QObject
-from time import time
+from time import time, sleep
from datetime import datetime
-from typing import Optional
+from typing import Optional, Dict, List
+import io #To create the correct buffers for sending data to the printer.
import json
import os
@@ -79,27 +85,51 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
self._latest_reply_handler = None
-
- def requestWrite(self, nodes, file_name=None, filter_by_machine=False, file_handler=None, **kwargs):
+ def requestWrite(self, nodes: List[SceneNode], file_name=None, filter_by_machine=False, file_handler=None, **kwargs):
self.writeStarted.emit(self)
- gcode_dict = getattr(Application.getInstance().getController().getScene(), "gcode_dict", [])
- active_build_plate_id = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
- gcode_list = gcode_dict[active_build_plate_id]
-
- if not gcode_list:
- # Unable to find g-code. Nothing to send
- return
-
- self._gcode = gcode_list
-
- if len(self._printers) > 1:
- self._spawnPrinterSelectionDialog()
+ #Formats supported by this application (file types that we can actually write).
+ if file_handler:
+ file_formats = file_handler.getSupportedFileTypesWrite()
else:
- self.sendPrintJob()
+ file_formats = Application.getInstance().getMeshFileHandler().getSupportedFileTypesWrite()
+
+ #Create a list from the supported file formats string.
+ machine_file_formats = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("file_formats").split(";")
+ machine_file_formats = [file_type.strip() for file_type in machine_file_formats]
+ #Exception for UM3 firmware version >=4.4: UFP is now supported and should be the preferred file format.
+ if "application/x-ufp" not in machine_file_formats and self.printerType == "ultimaker3" and Version(self.firmwareVersion) >= Version("4.4"):
+ machine_file_formats = ["application/x-ufp"] + machine_file_formats
+
+ # Take the intersection between file_formats and machine_file_formats.
+ format_by_mimetype = {format["mime_type"]: format for format in file_formats}
+ file_formats = [format_by_mimetype[mimetype] for mimetype in machine_file_formats] #Keep them ordered according to the preference in machine_file_formats.
+
+ if len(file_formats) == 0:
+ Logger.log("e", "There are no file formats available to write with!")
+ raise OutputDeviceError.WriteRequestFailedError(i18n_catalog.i18nc("@info:status", "There are no file formats available to write with!"))
+ preferred_format = file_formats[0]
+
+ #Just take the first file format available.
+ if file_handler is not None:
+ writer = file_handler.getWriterByMimeType(preferred_format["mime_type"])
+ else:
+ writer = Application.getInstance().getMeshFileHandler().getWriterByMimeType(preferred_format["mime_type"])
+
+ #This function pauses with the yield, waiting on instructions on which printer it needs to print with.
+ self._sending_job = self._sendPrintJob(writer, preferred_format, nodes)
+ self._sending_job.send(None) #Start the generator.
+
+ if len(self._printers) > 1: #We need to ask the user.
+ self._spawnPrinterSelectionDialog()
+ is_job_sent = True
+ else: #Just immediately continue.
+ self._sending_job.send("") #No specifically selected printer.
+ is_job_sent = self._sending_job.send(None)
# Notify the UI that a switch to the print monitor should happen
- Application.getInstance().getController().setActiveStage("MonitorStage")
+ if is_job_sent:
+ Application.getInstance().getController().setActiveStage("MonitorStage")
def _spawnPrinterSelectionDialog(self):
if self._printer_selection_dialog is None:
@@ -112,29 +142,54 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
def clusterSize(self):
return self._cluster_size
- @pyqtSlot()
+ ## Allows the user to choose a printer to print with from the printer
+ # selection dialogue.
+ # \param target_printer The name of the printer to target.
@pyqtSlot(str)
- def sendPrintJob(self, target_printer = ""):
+ def selectPrinter(self, target_printer: str = "") -> None:
+ self._sending_job.send(target_printer)
+
+ ## Greenlet to send a job to the printer over the network.
+ #
+ # This greenlet gets called asynchronously in requestWrite. It is a
+ # greenlet in order to optionally wait for selectPrinter() to select a
+ # printer.
+ # The greenlet yields exactly three times: First time None,
+ # \param writer The file writer to use to create the data.
+ # \param preferred_format A dictionary containing some information about
+ # what format to write to. This is necessary to create the correct buffer
+ # types and file extension and such.
+ def _sendPrintJob(self, writer: FileWriter, preferred_format: Dict, nodes: List[SceneNode]):
Logger.log("i", "Sending print job to printer.")
if self._sending_gcode:
self._error_message = Message(
i18n_catalog.i18nc("@info:status",
"Sending new jobs (temporarily) blocked, still sending the previous print job."))
self._error_message.show()
- return
+ yield #Wait on the user to select a target printer.
+ yield #Wait for the write job to be finished.
+ yield False #Return whether this was a success or not.
+ yield #Prevent StopIteration.
self._sending_gcode = True
- self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), 0, False, -1,
- i18n_catalog.i18nc("@info:title", "Sending Data"))
- self._progress_message.addAction("Abort", i18n_catalog.i18nc("@action:button", "Cancel"), None, "")
+ target_printer = yield #Potentially wait on the user to select a target printer.
+
+ # Using buffering greatly reduces the write time for many lines of gcode
+ if preferred_format["mode"] == FileWriter.OutputMode.TextMode:
+ stream = io.StringIO()
+ else: #Binary mode.
+ stream = io.BytesIO()
+
+ job = WriteFileJob(writer, stream, nodes, preferred_format["mode"])
+
+ self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), lifetime = 0, dismissable = False, progress = -1,
+ title = i18n_catalog.i18nc("@info:title", "Sending Data"))
+ self._progress_message.addAction("Abort", i18n_catalog.i18nc("@action:button", "Cancel"), icon = None, description = "")
self._progress_message.actionTriggered.connect(self._progressMessageActionTriggered)
self._progress_message.show()
- compressed_gcode = self._compressGCode()
- if compressed_gcode is None:
- # Abort was called.
- return
+ job.start()
parts = []
@@ -146,18 +201,27 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
# Add user name to the print_job
parts.append(self._createFormPart("name=owner", bytes(self._getUserName(), "utf-8"), "text/plain"))
- file_name = "%s.gcode.gz" % Application.getInstance().getPrintInformation().jobName
+ file_name = Application.getInstance().getPrintInformation().jobName + "." + preferred_format["extension"]
- parts.append(self._createFormPart("name=\"file\"; filename=\"%s\"" % file_name, compressed_gcode))
+ while not job.isFinished():
+ sleep(0.1)
+ output = stream.getvalue() #Either str or bytes depending on the output mode.
+ if isinstance(stream, io.StringIO):
+ output = output.encode("utf-8")
+
+ parts.append(self._createFormPart("name=\"file\"; filename=\"%s\"" % file_name, output))
self._latest_reply_handler = self.postFormWithParts("print_jobs/", parts, onFinished=self._onPostPrintJobFinished, onProgress=self._onUploadPrintJobProgress)
+ yield True #Return that we had success!
+ yield #To prevent having to catch the StopIteration exception.
+
@pyqtProperty(QObject, notify=activePrinterChanged)
- def activePrinter(self) -> Optional["PrinterOutputModel"]:
+ def activePrinter(self) -> Optional[PrinterOutputModel]:
return self._active_printer
@pyqtSlot(QObject)
- def setActivePrinter(self, printer):
+ def setActivePrinter(self, printer: Optional[PrinterOutputModel]):
if self._active_printer != printer:
if self._active_printer and self._active_printer.camera:
self._active_printer.camera.stop()
@@ -169,7 +233,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
self._compressing_gcode = False
self._sending_gcode = False
- def _onUploadPrintJobProgress(self, bytes_sent, bytes_total):
+ def _onUploadPrintJobProgress(self, bytes_sent:int, bytes_total:int):
if bytes_total > 0:
new_progress = bytes_sent / bytes_total * 100
# Treat upload progress as response. Uploading can take more than 10 seconds, so if we don't, we can get
@@ -182,7 +246,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
self._progress_message.setProgress(0)
self._progress_message.hide()
- def _progressMessageActionTriggered(self, message_id=None, action_id=None):
+ def _progressMessageActionTriggered(self, message_id: Optional[str]=None, action_id: Optional[str]=None) -> None:
if action_id == "Abort":
Logger.log("d", "User aborted sending print to remote.")
self._progress_message.hide()
@@ -194,32 +258,33 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
# the "reply" should be disconnected
if self._latest_reply_handler:
self._latest_reply_handler.disconnect()
+ self._latest_reply_handler = None
@pyqtSlot()
- def openPrintJobControlPanel(self):
+ def openPrintJobControlPanel(self) -> None:
Logger.log("d", "Opening print job control panel...")
QDesktopServices.openUrl(QUrl("http://" + self._address + "/print_jobs"))
@pyqtSlot()
- def openPrinterControlPanel(self):
+ def openPrinterControlPanel(self) -> None:
Logger.log("d", "Opening printer control panel...")
QDesktopServices.openUrl(QUrl("http://" + self._address + "/printers"))
@pyqtProperty("QVariantList", notify=printJobsChanged)
- def printJobs(self):
+ def printJobs(self)-> List[PrintJobOutputModel] :
return self._print_jobs
@pyqtProperty("QVariantList", notify=printJobsChanged)
- def queuedPrintJobs(self):
- return [print_job for print_job in self._print_jobs if print_job.assignedPrinter is None or print_job.state == "queued"]
+ def queuedPrintJobs(self) -> List[PrintJobOutputModel]:
+ return [print_job for print_job in self._print_jobs if print_job.state == "queued"]
@pyqtProperty("QVariantList", notify=printJobsChanged)
- def activePrintJobs(self):
+ def activePrintJobs(self) -> List[PrintJobOutputModel]:
return [print_job for print_job in self._print_jobs if print_job.assignedPrinter is not None and print_job.state != "queued"]
@pyqtProperty("QVariantList", notify=clusterPrintersChanged)
- def connectedPrintersTypeCount(self):
+ def connectedPrintersTypeCount(self) -> List[PrinterOutputModel]:
printer_count = {}
for printer in self._printers:
if printer.type in printer_count:
@@ -232,22 +297,22 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
return result
@pyqtSlot(int, result=str)
- def formatDuration(self, seconds):
+ def formatDuration(self, seconds: int) -> str:
return Duration(seconds).getDisplayString(DurationFormat.Format.Short)
@pyqtSlot(int, result=str)
- def getTimeCompleted(self, time_remaining):
+ def getTimeCompleted(self, time_remaining: int) -> str:
current_time = time()
datetime_completed = datetime.fromtimestamp(current_time + time_remaining)
return "{hour:02d}:{minute:02d}".format(hour=datetime_completed.hour, minute=datetime_completed.minute)
@pyqtSlot(int, result=str)
- def getDateCompleted(self, time_remaining):
+ def getDateCompleted(self, time_remaining: int) -> str:
current_time = time()
datetime_completed = datetime.fromtimestamp(current_time + time_remaining)
return (datetime_completed.strftime("%a %b ") + "{day}".format(day=datetime_completed.day)).upper()
- def _printJobStateChanged(self):
+ def _printJobStateChanged(self) -> None:
username = self._getUserName()
if username is None:
@@ -270,13 +335,13 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
# Keep a list of all completed jobs so we know if something changed next time.
self._finished_jobs = finished_jobs
- def _update(self):
+ def _update(self) -> None:
if not super()._update():
return
self.get("printers/", onFinished=self._onGetPrintersDataFinished)
self.get("print_jobs/", onFinished=self._onGetPrintJobsFinished)
- def _onGetPrintJobsFinished(self, reply: QNetworkReply):
+ def _onGetPrintJobsFinished(self, reply: QNetworkReply) -> None:
if not checkValidGetReply(reply):
return
@@ -296,8 +361,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
self._updatePrintJob(print_job, print_job_data)
if print_job.state != "queued": # Print job should be assigned to a printer.
- if print_job.state == "failed":
- # Print job was failed, so don't attach it to a printer.
+ if print_job.state in ["failed", "finished", "aborted"]:
+ # Print job was already completed, so don't attach it to a printer.
printer = None
else:
printer = self._getPrinterByKey(print_job_data["printer_uuid"])
@@ -318,7 +383,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
if job_list_changed:
self.printJobsChanged.emit() # Do a single emit for all print job changes.
- def _onGetPrintersDataFinished(self, reply: QNetworkReply):
+ def _onGetPrintersDataFinished(self, reply: QNetworkReply) -> None:
if not checkValidGetReply(reply):
return
@@ -347,34 +412,45 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
if removed_printers or printer_list_changed:
self.printersChanged.emit()
- def _createPrinterModel(self, data):
+ def _createPrinterModel(self, data: Dict) -> PrinterOutputModel:
printer = PrinterOutputModel(output_controller=ClusterUM3PrinterOutputController(self),
number_of_extruders=self._number_of_extruders)
printer.setCamera(NetworkCamera("http://" + data["ip_address"] + ":8080/?action=stream"))
self._printers.append(printer)
return printer
- def _createPrintJobModel(self, data):
+ def _createPrintJobModel(self, data: Dict) -> PrintJobOutputModel:
print_job = PrintJobOutputModel(output_controller=ClusterUM3PrinterOutputController(self),
key=data["uuid"], name= data["name"])
print_job.stateChanged.connect(self._printJobStateChanged)
self._print_jobs.append(print_job)
return print_job
- def _updatePrintJob(self, print_job, data):
+ def _updatePrintJob(self, print_job: PrintJobOutputModel, data: Dict) -> None:
print_job.updateTimeTotal(data["time_total"])
print_job.updateTimeElapsed(data["time_elapsed"])
print_job.updateState(data["status"])
print_job.updateOwner(data["owner"])
- def _updatePrinter(self, printer, data):
+ def _updatePrinter(self, printer: PrinterOutputModel, data: Dict) -> None:
# For some unknown reason the cluster wants UUID for everything, except for sending a job directly to a printer.
# Then we suddenly need the unique name. So in order to not have to mess up all the other code, we save a mapping.
self._printer_uuid_to_unique_name_mapping[data["uuid"]] = data["unique_name"]
+ definitions = ContainerRegistry.getInstance().findDefinitionContainers(name = data["machine_variant"])
+ if not definitions:
+ Logger.log("w", "Unable to find definition for machine variant %s", data["machine_variant"])
+ return
+
+ machine_definition = definitions[0]
+
printer.updateName(data["friendly_name"])
printer.updateKey(data["uuid"])
printer.updateType(data["machine_variant"])
+
+ # Do not store the buildplate information that comes from connect if the current printer has not buildplate information
+ if "build_plate" in data and machine_definition.getMetaDataEntry("has_variant_buildplates", False):
+ printer.updateBuildplateName(data["build_plate"]["type"])
if not data["enabled"]:
printer.updateState("disabled")
else:
@@ -411,7 +487,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
brand=brand, color=color, name=name)
extruder.updateActiveMaterial(material)
- def _removeJob(self, job):
+ def _removeJob(self, job: PrintJobOutputModel):
if job not in self._print_jobs:
return False
@@ -422,7 +498,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
return True
- def _removePrinter(self, printer):
+ def _removePrinter(self, printer: PrinterOutputModel):
self._printers.remove(printer)
if self._active_printer == printer:
self._active_printer = None
diff --git a/plugins/UM3NetworkPrinting/ClusterUM3PrinterOutputController.py b/plugins/UM3NetworkPrinting/ClusterUM3PrinterOutputController.py
index 4615cd62dc..707443b9ea 100644
--- a/plugins/UM3NetworkPrinting/ClusterUM3PrinterOutputController.py
+++ b/plugins/UM3NetworkPrinting/ClusterUM3PrinterOutputController.py
@@ -13,7 +13,9 @@ class ClusterUM3PrinterOutputController(PrinterOutputController):
def __init__(self, output_device):
super().__init__(output_device)
self.can_pre_heat_bed = False
+ self.can_pre_heat_hotends = False
self.can_control_manually = False
+ self.can_send_raw_gcode = False
def setJobState(self, job: "PrintJobOutputModel", state: str):
data = "{\"action\": \"%s\"}" % state
diff --git a/plugins/UM3NetworkPrinting/DiscoverUM3Action.py b/plugins/UM3NetworkPrinting/DiscoverUM3Action.py
index 0e872fed43..0b8d6e9f53 100644
--- a/plugins/UM3NetworkPrinting/DiscoverUM3Action.py
+++ b/plugins/UM3NetworkPrinting/DiscoverUM3Action.py
@@ -97,6 +97,25 @@ class DiscoverUM3Action(MachineAction):
else:
return []
+ @pyqtSlot(str)
+ def setGroupName(self, group_name):
+ Logger.log("d", "Attempting to set the group name of the active machine to %s", group_name)
+ global_container_stack = Application.getInstance().getGlobalContainerStack()
+ if global_container_stack:
+ meta_data = global_container_stack.getMetaData()
+ if "connect_group_name" in meta_data:
+ previous_connect_group_name = meta_data["connect_group_name"]
+ global_container_stack.setMetaDataEntry("connect_group_name", group_name)
+ # Find all the places where there is the same group name and change it accordingly
+ Application.getInstance().getMachineManager().replaceContainersMetadata(key = "connect_group_name", value = previous_connect_group_name, new_value = group_name)
+ else:
+ global_container_stack.addMetaDataEntry("connect_group_name", group_name)
+ global_container_stack.addMetaDataEntry("hidden", False)
+
+ if self._network_plugin:
+ # Ensure that the connection states are refreshed.
+ self._network_plugin.reCheckConnections()
+
@pyqtSlot(str)
def setKey(self, key):
Logger.log("d", "Attempting to set the network key of the active machine to %s", key)
@@ -104,11 +123,13 @@ class DiscoverUM3Action(MachineAction):
if global_container_stack:
meta_data = global_container_stack.getMetaData()
if "um_network_key" in meta_data:
+ previous_network_key= meta_data["um_network_key"]
global_container_stack.setMetaDataEntry("um_network_key", key)
# Delete old authentication data.
Logger.log("d", "Removing old authentication id %s for device %s", global_container_stack.getMetaDataEntry("network_authentication_id", None), key)
global_container_stack.removeMetaDataEntry("network_authentication_id")
global_container_stack.removeMetaDataEntry("network_authentication_key")
+ Application.getInstance().getMachineManager().replaceContainersMetadata(key = "um_network_key", value = previous_network_key, new_value = key)
else:
global_container_stack.addMetaDataEntry("um_network_key", key)
@@ -126,6 +147,10 @@ class DiscoverUM3Action(MachineAction):
return ""
+ @pyqtSlot(str, result = bool)
+ def existsKey(self, key) -> bool:
+ return Application.getInstance().getMachineManager().existNetworkInstances(network_key = key)
+
@pyqtSlot()
def loadConfigurationFromPrinter(self):
machine_manager = Application.getInstance().getMachineManager()
diff --git a/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml b/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml
index c0cb5a78b7..0aaeef8fbd 100644
--- a/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml
+++ b/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml
@@ -5,6 +5,7 @@ import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Layouts 1.1
import QtQuick.Window 2.1
+import QtQuick.Dialogs 1.2
Cura.MachineAction
{
@@ -32,14 +33,34 @@ Cura.MachineAction
if(base.selectedDevice && base.completeProperties)
{
var printerKey = base.selectedDevice.key
- if(manager.getStoredKey() != printerKey)
+ var printerName = base.selectedDevice.name // TODO To change when the groups have a name
+ if (manager.getStoredKey() != printerKey)
{
- manager.setKey(printerKey);
- completed();
+ // Check if there is another instance with the same key
+ if (!manager.existsKey(printerKey))
+ {
+ manager.setKey(printerKey)
+ manager.setGroupName(printerName) // TODO To change when the groups have a name
+ completed()
+ }
+ else
+ {
+ existingConnectionDialog.open()
+ }
}
}
}
+ MessageDialog
+ {
+ id: existingConnectionDialog
+ title: catalog.i18nc("@window:title", "Existing Connection")
+ icon: StandardIcon.Information
+ text: catalog.i18nc("@message:text", "This printer/group is already added to Cura. Please select another printer/group.")
+ standardButtons: StandardButton.Ok
+ modality: Qt.ApplicationModal
+ }
+
Column
{
anchors.fill: parent;
@@ -303,7 +324,7 @@ Cura.MachineAction
Button
{
text: catalog.i18nc("@action:button", "Connect")
- enabled: (base.selectedDevice && base.completeProperties) ? true : false
+ enabled: (base.selectedDevice && base.completeProperties && base.selectedDevice.clusterSize > 0) ? true : false
onClicked: connectToPrinter()
}
}
diff --git a/plugins/UM3NetworkPrinting/LegacyUM3PrinterOutputController.py b/plugins/UM3NetworkPrinting/LegacyUM3PrinterOutputController.py
index 7a0e113d5b..b12a31b6cf 100644
--- a/plugins/UM3NetworkPrinting/LegacyUM3PrinterOutputController.py
+++ b/plugins/UM3NetworkPrinting/LegacyUM3PrinterOutputController.py
@@ -20,6 +20,7 @@ class LegacyUM3PrinterOutputController(PrinterOutputController):
self._preheat_printer = None
self.can_control_manually = False
+ self.can_send_raw_gcode = False
# Are we still waiting for a response about preheat?
# We need this so we can already update buttons, so it feels more snappy.
diff --git a/plugins/UM3NetworkPrinting/PrintWindow.qml b/plugins/UM3NetworkPrinting/PrintWindow.qml
index d84b0f30ec..43afbcdfe0 100644
--- a/plugins/UM3NetworkPrinting/PrintWindow.qml
+++ b/plugins/UM3NetworkPrinting/PrintWindow.qml
@@ -101,7 +101,7 @@ UM.Dialog
enabled: true
onClicked: {
base.visible = false;
- OutputDevice.sendPrintJob(printerSelectionCombobox.model.get(printerSelectionCombobox.currentIndex).key)
+ OutputDevice.selectPrinter(printerSelectionCombobox.model.get(printerSelectionCombobox.currentIndex).key)
// reset to defaults
printerSelectionCombobox.currentIndex = 0
}
diff --git a/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml b/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml
index 54a34fae46..0217767a40 100644
--- a/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml
+++ b/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml
@@ -34,17 +34,17 @@ Rectangle
switch (printer.state)
{
case "pre_print":
- return catalog.i18nc("@label", "Preparing to print")
+ return catalog.i18nc("@label:status", "Preparing to print")
case "printing":
return catalog.i18nc("@label:status", "Printing");
case "idle":
return catalog.i18nc("@label:status", "Available");
case "unreachable":
- return catalog.i18nc("@label:MonitorStatus", "Lost connection with the printer");
- case "maintenance": // TODO: new string
- case "unknown":
+ return catalog.i18nc("@label:status", "Lost connection with the printer");
+ case "maintenance":
+ return catalog.i18nc("@label:status", "Unavailable");
default:
- return catalog.i18nc("@label Printer status", "Unknown");
+ return catalog.i18nc("@label:status", "Unknown");
}
}
diff --git a/plugins/UM3NetworkPrinting/UM3OutputDevicePlugin.py b/plugins/UM3NetworkPrinting/UM3OutputDevicePlugin.py
index 5ff5eb9e3e..089b9038f7 100644
--- a/plugins/UM3NetworkPrinting/UM3OutputDevicePlugin.py
+++ b/plugins/UM3NetworkPrinting/UM3OutputDevicePlugin.py
@@ -82,6 +82,9 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self._zero_conf_browser.cancel()
self._zero_conf_browser = None # Force the old ServiceBrowser to be destroyed.
+ for instance_name in list(self._discovered_devices):
+ self._onRemoveDevice(instance_name)
+
self._zero_conf = Zeroconf()
self._zero_conf_browser = ServiceBrowser(self._zero_conf, u'_ultimaker._tcp.local.',
[self._appendServiceChangedRequest])
diff --git a/plugins/USBPrinting/AutoDetectBaudJob.py b/plugins/USBPrinting/AutoDetectBaudJob.py
index 72f4f20262..50bb831ba8 100644
--- a/plugins/USBPrinting/AutoDetectBaudJob.py
+++ b/plugins/USBPrinting/AutoDetectBaudJob.py
@@ -22,6 +22,7 @@ class AutoDetectBaudJob(Job):
def run(self):
Logger.log("d", "Auto detect baud rate started.")
timeout = 3
+ tries = 2
programmer = Stk500v2()
serial = None
@@ -31,36 +32,38 @@ class AutoDetectBaudJob(Job):
except:
programmer.close()
- for baud_rate in self._all_baud_rates:
- Logger.log("d", "Checking {serial} if baud rate {baud_rate} works".format(serial= self._serial_port, baud_rate = baud_rate))
+ for retry in range(tries):
+ for baud_rate in self._all_baud_rates:
+ Logger.log("d", "Checking {serial} if baud rate {baud_rate} works".format(serial= self._serial_port, baud_rate = baud_rate))
- if serial is None:
- try:
- serial = Serial(str(self._serial_port), baud_rate, timeout = timeout, writeTimeout = timeout)
- except SerialException as e:
- Logger.logException("w", "Unable to create serial")
- continue
- else:
- # We already have a serial connection, just change the baud rate.
- try:
- serial.baudrate = baud_rate
- except:
- continue
- sleep(1.5) # Ensure that we are not talking to the boot loader. 1.5 seconds seems to be the magic number
- successful_responses = 0
-
- serial.write(b"\n") # Ensure we clear out previous responses
- serial.write(b"M105\n")
-
- timeout_time = time() + timeout
-
- while timeout_time > time():
- line = serial.readline()
- if b"ok T:" in line:
- successful_responses += 1
- if successful_responses >= 3:
- self.setResult(baud_rate)
- return
+ if serial is None:
+ try:
+ serial = Serial(str(self._serial_port), baud_rate, timeout = timeout, writeTimeout = timeout)
+ except SerialException as e:
+ Logger.logException("w", "Unable to create serial")
+ continue
+ else:
+ # We already have a serial connection, just change the baud rate.
+ try:
+ serial.baudrate = baud_rate
+ except:
+ continue
+ sleep(1.5) # Ensure that we are not talking to the boot loader. 1.5 seconds seems to be the magic number
+ successful_responses = 0
+ serial.write(b"\n") # Ensure we clear out previous responses
serial.write(b"M105\n")
+
+ timeout_time = time() + timeout
+
+ while timeout_time > time():
+ line = serial.readline()
+ if b"ok T:" in line:
+ successful_responses += 1
+ if successful_responses >= 3:
+ self.setResult(baud_rate)
+ return
+
+ serial.write(b"M105\n")
+ sleep(15) # Give the printer some time to init and try again.
self.setResult(None) # Unable to detect the correct baudrate.
diff --git a/plugins/USBPrinting/USBPrinterOutputController.py b/plugins/USBPrinting/USBPrinterOutputController.py
deleted file mode 100644
index f189ed5876..0000000000
--- a/plugins/USBPrinting/USBPrinterOutputController.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# Copyright (c) 2017 Ultimaker B.V.
-# Cura is released under the terms of the LGPLv3 or higher.
-
-from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
-from PyQt5.QtCore import QTimer
-
-MYPY = False
-if MYPY:
- from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
- from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
-
-
-class USBPrinterOutputController(PrinterOutputController):
- def __init__(self, output_device):
- super().__init__(output_device)
-
- self._preheat_bed_timer = QTimer()
- self._preheat_bed_timer.setSingleShot(True)
- self._preheat_bed_timer.timeout.connect(self._onPreheatBedTimerFinished)
- self._preheat_printer = None
-
- def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed):
- self._output_device.sendCommand("G91")
- self._output_device.sendCommand("G0 X%s Y%s Z%s F%s" % (x, y, z, speed))
- self._output_device.sendCommand("G90")
-
- def homeHead(self, printer):
- self._output_device.sendCommand("G28 X")
- self._output_device.sendCommand("G28 Y")
-
- def homeBed(self, printer):
- self._output_device.sendCommand("G28 Z")
-
- def setJobState(self, job: "PrintJobOutputModel", state: str):
- if state == "pause":
- self._output_device.pausePrint()
- job.updateState("paused")
- elif state == "print":
- self._output_device.resumePrint()
- job.updateState("printing")
- elif state == "abort":
- self._output_device.cancelPrint()
- pass
-
- def preheatBed(self, printer: "PrinterOutputModel", temperature, duration):
- try:
- temperature = round(temperature) # The API doesn't allow floating point.
- duration = round(duration)
- except ValueError:
- return # Got invalid values, can't pre-heat.
-
- self.setTargetBedTemperature(printer, temperature=temperature)
- self._preheat_bed_timer.setInterval(duration * 1000)
- self._preheat_bed_timer.start()
- self._preheat_printer = printer
- printer.updateIsPreheating(True)
-
- def cancelPreheatBed(self, printer: "PrinterOutputModel"):
- self.preheatBed(printer, temperature=0, duration=0)
- self._preheat_bed_timer.stop()
- printer.updateIsPreheating(False)
-
- def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int):
- self._output_device.sendCommand("M140 S%s" % temperature)
-
- def _onPreheatBedTimerFinished(self):
- self.setTargetBedTemperature(self._preheat_printer, 0)
- self._preheat_printer.updateIsPreheating(False)
\ No newline at end of file
diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py
index 6e2b5153db..24feedd628 100644
--- a/plugins/USBPrinting/USBPrinterOutputDevice.py
+++ b/plugins/USBPrinting/USBPrinterOutputDevice.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2016 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Logger import Logger
@@ -10,14 +10,14 @@ from UM.PluginRegistry import PluginRegistry
from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
+from cura.PrinterOutput.GenericOutputController import GenericOutputController
from .AutoDetectBaudJob import AutoDetectBaudJob
-from .USBPrinterOutputController import USBPrinterOutputController
from .avr_isp import stk500v2, intelHex
from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty
-from serial import Serial, SerialException
+from serial import Serial, SerialException, SerialTimeoutException
from threading import Thread
from time import time, sleep
from queue import Queue
@@ -116,7 +116,8 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
@pyqtSlot(str)
def updateFirmware(self, file):
- self._firmware_location = file
+ # the file path is qurl encoded.
+ self._firmware_location = file.replace("file://", "")
self.showFirmwareInterface()
self.setFirmwareUpdateState(FirmwareUpdateState.updating)
self._update_firmware_thread.start()
@@ -126,9 +127,11 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
if self._connection_state != ConnectionState.closed:
self.close()
- hex_file = intelHex.readHex(self._firmware_location)
- if len(hex_file) == 0:
- Logger.log("e", "Unable to read provided hex file. Could not update firmware")
+ try:
+ hex_file = intelHex.readHex(self._firmware_location)
+ assert len(hex_file) > 0
+ except (FileNotFoundError, AssertionError):
+ Logger.log("e", "Unable to read provided hex file. Could not update firmware.")
self.setFirmwareUpdateState(FirmwareUpdateState.firmware_not_found_error)
return
@@ -198,7 +201,6 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
# Reset line number. If this is not done, first line is sometimes ignored
self._gcode.insert(0, "M110")
self._gcode_position = 0
- self._is_printing = True
self._print_start_time = time()
self._print_estimated_time = int(Application.getInstance().getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.Seconds))
@@ -206,6 +208,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
for i in range(0, 4): # Push first 4 entries before accepting other inputs
self._sendNextGcodeLine()
+ self._is_printing = True
self.writeFinished.emit(self)
def _autoDetectFinished(self, job: AutoDetectBaudJob):
@@ -237,7 +240,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
container_stack = Application.getInstance().getGlobalContainerStack()
num_extruders = container_stack.getProperty("machine_extruder_count", "value")
# Ensure that a printer is created.
- self._printers = [PrinterOutputModel(output_controller=USBPrinterOutputController(self), number_of_extruders=num_extruders)]
+ self._printers = [PrinterOutputModel(output_controller=GenericOutputController(self), number_of_extruders=num_extruders)]
self._printers[0].updateName(container_stack.getName())
self.setConnectionState(ConnectionState.connected)
self._update_thread.start()
@@ -266,8 +269,10 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
command = (command + "\n").encode()
if not command.endswith(b"\n"):
command += b"\n"
- self._serial.write(b"\n")
- self._serial.write(command)
+ try:
+ self._serial.write(command)
+ except SerialTimeoutException:
+ Logger.log("w", "Timeout when sending command to printer via USB.")
def _update(self):
while self._connection_state == ConnectionState.connected and self._serial is not None:
@@ -281,7 +286,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
self.sendCommand("M105")
self._last_temperature_request = time()
- if b"ok T:" in line or line.startswith(b"T:"): # Temperature message
+ if b"ok T:" in line or line.startswith(b"T:") or b"ok B:" in line or line.startswith(b"B:"): # Temperature message. 'T:' for extruder and 'B:' for bed
extruder_temperature_matches = re.findall(b"T(\d*): ?([\d\.]+) ?\/?([\d\.]+)?", line)
# Update all temperature values
for match, extruder in zip(extruder_temperature_matches, self._printers[0].extruders):
@@ -299,6 +304,9 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
self._printers[0].updateTargetBedTemperature(float(match[1]))
if self._is_printing:
+ if line.startswith(b'!!'):
+ Logger.log('e', "Printer signals fatal error. Cancelling print. {}".format(line))
+ self.cancelPrint()
if b"ok" in line:
if not self._command_queue.empty():
self._sendCommand(self._command_queue.get())
@@ -364,7 +372,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
elapsed_time = int(time() - self._print_start_time)
print_job = self._printers[0].activePrintJob
if print_job is None:
- print_job = PrintJobOutputModel(output_controller = USBPrinterOutputController(self), name= Application.getInstance().getPrintInformation().jobName)
+ print_job = PrintJobOutputModel(output_controller = GenericOutputController(self), name= Application.getInstance().getPrintInformation().jobName)
print_job.updateState("printing")
self._printers[0].updateActivePrintJob(print_job)
diff --git a/plugins/UltimakerMachineActions/BedLevelMachineAction.py b/plugins/UltimakerMachineActions/BedLevelMachineAction.py
index 04b6cf1acc..6a8a337d8c 100644
--- a/plugins/UltimakerMachineActions/BedLevelMachineAction.py
+++ b/plugins/UltimakerMachineActions/BedLevelMachineAction.py
@@ -1,3 +1,8 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from typing import List
+
from cura.MachineAction import MachineAction
from cura.PrinterOutputDevice import PrinterOutputDevice
@@ -5,6 +10,7 @@ from UM.FlameProfiler import pyqtSlot
from UM.Application import Application
from UM.i18n import i18nCatalog
+from UM.Logger import Logger
catalog = i18nCatalog("cura")
@@ -26,38 +32,45 @@ class BedLevelMachineAction(MachineAction):
@pyqtSlot()
def startBedLeveling(self):
self._bed_level_position = 0
- printer_output_devices = self._getPrinterOutputDevices()
- if printer_output_devices:
- printer_output_devices[0].homeBed()
- printer_output_devices[0].moveHead(0, 0, 3)
- printer_output_devices[0].homeHead()
- def _getPrinterOutputDevices(self):
+ printer_output_devices = self._getPrinterOutputDevices()
+ if not printer_output_devices:
+ Logger.log("e", "Can't start bed levelling. The printer connection seems to have been lost.")
+ return
+ printer = printer_output_devices[0].activePrinter
+
+ printer.homeBed()
+ printer.moveHead(0, 0, 3)
+ printer.homeHead()
+
+ def _getPrinterOutputDevices(self) -> List[PrinterOutputDevice]:
return [printer_output_device for printer_output_device in Application.getInstance().getOutputDeviceManager().getOutputDevices() if isinstance(printer_output_device, PrinterOutputDevice)]
@pyqtSlot()
def moveToNextLevelPosition(self):
output_devices = self._getPrinterOutputDevices()
- if output_devices: # We found at least one output device
- output_device = output_devices[0]
+ if not output_devices: #No output devices. Can't move.
+ Logger.log("e", "Can't move to the next position. The printer connection seems to have been lost.")
+ return
+ printer = output_devices[0].activePrinter
- if self._bed_level_position == 0:
- output_device.moveHead(0, 0, 3)
- output_device.homeHead()
- output_device.moveHead(0, 0, 3)
- output_device.moveHead(Application.getInstance().getGlobalContainerStack().getProperty("machine_width", "value") - 10, 0, 0)
- output_device.moveHead(0, 0, -3)
- self._bed_level_position += 1
- elif self._bed_level_position == 1:
- output_device.moveHead(0, 0, 3)
- output_device.moveHead(-Application.getInstance().getGlobalContainerStack().getProperty("machine_width", "value" ) / 2, Application.getInstance().getGlobalContainerStack().getProperty("machine_depth", "value") - 10, 0)
- output_device.moveHead(0, 0, -3)
- self._bed_level_position += 1
- elif self._bed_level_position == 2:
- output_device.moveHead(0, 0, 3)
- output_device.moveHead(-Application.getInstance().getGlobalContainerStack().getProperty("machine_width", "value") / 2 + 10, -(Application.getInstance().getGlobalContainerStack().getProperty("machine_depth", "value") + 10), 0)
- output_device.moveHead(0, 0, -3)
- self._bed_level_position += 1
- elif self._bed_level_position >= 3:
- output_device.sendCommand("M18") # Turn off all motors so the user can move the axes
- self.setFinished()
\ No newline at end of file
+ if self._bed_level_position == 0:
+ printer.moveHead(0, 0, 3)
+ printer.homeHead()
+ printer.moveHead(0, 0, 3)
+ printer.moveHead(Application.getInstance().getGlobalContainerStack().getProperty("machine_width", "value") - 10, 0, 0)
+ printer.moveHead(0, 0, -3)
+ self._bed_level_position += 1
+ elif self._bed_level_position == 1:
+ printer.moveHead(0, 0, 3)
+ printer.moveHead(-Application.getInstance().getGlobalContainerStack().getProperty("machine_width", "value" ) / 2, Application.getInstance().getGlobalContainerStack().getProperty("machine_depth", "value") - 10, 0)
+ printer.moveHead(0, 0, -3)
+ self._bed_level_position += 1
+ elif self._bed_level_position == 2:
+ printer.moveHead(0, 0, 3)
+ printer.moveHead(-Application.getInstance().getGlobalContainerStack().getProperty("machine_width", "value") / 2 + 10, -(Application.getInstance().getGlobalContainerStack().getProperty("machine_depth", "value") + 10), 0)
+ printer.moveHead(0, 0, -3)
+ self._bed_level_position += 1
+ elif self._bed_level_position >= 3:
+ output_devices[0].sendCommand("M18") # Turn off all motors so the user can move the axes
+ self.setFinished()
\ No newline at end of file
diff --git a/plugins/VersionUpgrade/VersionUpgrade26to27/VersionUpgrade26to27.py b/plugins/VersionUpgrade/VersionUpgrade26to27/VersionUpgrade26to27.py
index a2d78d8d9f..2037a0211d 100644
--- a/plugins/VersionUpgrade/VersionUpgrade26to27/VersionUpgrade26to27.py
+++ b/plugins/VersionUpgrade/VersionUpgrade26to27/VersionUpgrade26to27.py
@@ -153,6 +153,10 @@ class VersionUpgrade26to27(VersionUpgrade):
if new_id is not None:
parser.set("containers", key, new_id)
+ if "6" not in parser["containers"]:
+ parser["containers"]["6"] = parser["containers"]["5"]
+ parser["containers"]["5"] = "empty"
+
for each_section in ("general", "metadata"):
if not parser.has_section(each_section):
parser.add_section(each_section)
diff --git a/plugins/VersionUpgrade/VersionUpgrade30to31/__init__.py b/plugins/VersionUpgrade/VersionUpgrade30to31/__init__.py
index b4b75dddf7..c853e2b93b 100644
--- a/plugins/VersionUpgrade/VersionUpgrade30to31/__init__.py
+++ b/plugins/VersionUpgrade/VersionUpgrade30to31/__init__.py
@@ -33,6 +33,10 @@ def getMetaData():
"get_version": upgrade.getCfgVersion,
"location": {"./extruders"}
},
+ "quality": {
+ "get_version": upgrade.getCfgVersion,
+ "location": {"./quality"}
+ },
"quality_changes": {
"get_version": upgrade.getCfgVersion,
"location": {"./quality"}
diff --git a/plugins/VersionUpgrade/VersionUpgrade32to33/VersionUpgrade32to33.py b/plugins/VersionUpgrade/VersionUpgrade32to33/VersionUpgrade32to33.py
new file mode 100644
index 0000000000..e39266884d
--- /dev/null
+++ b/plugins/VersionUpgrade/VersionUpgrade32to33/VersionUpgrade32to33.py
@@ -0,0 +1,138 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+import configparser #To parse preference files.
+import io #To serialise the preference files afterwards.
+
+from UM.VersionUpgrade import VersionUpgrade #We're inheriting from this.
+
+## Mapping extruder definition IDs to the positions that they are in.
+_EXTRUDER_TO_POSITION = {
+ "builder_premium_large_front": 1,
+ "builder_premium_large_rear": 0,
+ "builder_premium_medium_front": 1,
+ "builder_premium_medium_rear": 0,
+ "builder_premium_small_front": 1,
+ "builder_premium_small_rear": 0,
+ "cartesio_extruder_0": 0,
+ "cartesio_extruder_1": 1,
+ "cartesio_extruder_2": 2,
+ "cartesio_extruder_3": 3,
+ "custom_extruder_1": 0, #Warning, non-programmers are attempting to count here.
+ "custom_extruder_2": 1,
+ "custom_extruder_3": 2,
+ "custom_extruder_4": 3,
+ "custom_extruder_5": 4,
+ "custom_extruder_6": 5,
+ "custom_extruder_7": 6,
+ "custom_extruder_8": 7,
+ "hBp_extruder_left": 0,
+ "hBp_extruder_right": 1,
+ "makeit_dual_1st": 0,
+ "makeit_dual_2nd": 1,
+ "makeit_l_dual_1st": 0,
+ "makeit_l_dual_2nd": 1,
+ "ord_extruder_0": 0,
+ "ord_extruder_1": 1,
+ "ord_extruder_2": 2,
+ "ord_extruder_3": 3,
+ "ord_extruder_4": 4,
+ "punchtec_connect_xl_extruder_left": 0,
+ "punchtec_connect_xl_extruder_right": 1,
+ "raise3D_N2_dual_extruder_0": 0,
+ "raise3D_N2_dual_extruder_1": 1,
+ "raise3D_N2_plus_dual_extruder_0": 0,
+ "raise3D_N2_plus_dual_extruder_1": 1,
+ "ultimaker3_extended_extruder_left": 0,
+ "ultimaker3_extended_extruder_right": 1,
+ "ultimaker3_extruder_left": 0,
+ "ultimaker3_extruder_right": 1,
+ "ultimaker_original_dual_1st": 0,
+ "ultimaker_original_dual_2nd": 1,
+ "vertex_k8400_dual_1st": 0,
+ "vertex_k8400_dual_2nd": 1
+}
+
+## Upgrades configurations from the state they were in at version 3.2 to the
+# state they should be in at version 3.3.
+class VersionUpgrade32to33(VersionUpgrade):
+
+ temporary_group_name_counter = 1
+ ## Gets the version number from a CFG file in Uranium's 3.2 format.
+ #
+ # Since the format may change, this is implemented for the 3.2 format only
+ # and needs to be included in the version upgrade system rather than
+ # globally in Uranium.
+ #
+ # \param serialised The serialised form of a CFG file.
+ # \return The version number stored in the CFG file.
+ # \raises ValueError The format of the version number in the file is
+ # incorrect.
+ # \raises KeyError The format of the file is incorrect.
+ def getCfgVersion(self, serialised):
+ parser = configparser.ConfigParser(interpolation = None)
+ parser.read_string(serialised)
+ format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
+ setting_version = int(parser.get("metadata", "setting_version", fallback = 0))
+ return format_version * 1000000 + setting_version
+
+ ## Upgrades a container stack from version 3.2 to 3.3.
+ #
+ # \param serialised The serialised form of a container stack.
+ # \param filename The name of the file to upgrade.
+ def upgradeStack(self, serialized, filename):
+ parser = configparser.ConfigParser(interpolation = None)
+ parser.read_string(serialized)
+
+ if "metadata" in parser and "um_network_key" in parser["metadata"]:
+ if "hidden" not in parser["metadata"]:
+ parser["metadata"]["hidden"] = "False"
+ if "connect_group_name" not in parser["metadata"]:
+ parser["metadata"]["connect_group_name"] = "Temporary group name #" + str(self.temporary_group_name_counter)
+ self.temporary_group_name_counter += 1
+
+ #Update version number.
+ parser["general"]["version"] = "4"
+
+ result = io.StringIO()
+ parser.write(result)
+ return [filename], [result.getvalue()]
+
+ ## Upgrades non-quality-changes instance containers to have the new version
+ # number.
+ def upgradeInstanceContainer(self, serialized, filename):
+ parser = configparser.ConfigParser(interpolation = None)
+ parser.read_string(serialized)
+
+ #Update version number.
+ parser["general"]["version"] = "3"
+
+ result = io.StringIO()
+ parser.write(result)
+ return [filename], [result.getvalue()]
+
+ ## Upgrades a quality changes container to the new format.
+ def upgradeQualityChanges(self, serialized, filename):
+ parser = configparser.ConfigParser(interpolation = None)
+ parser.read_string(serialized)
+
+ #Extruder quality changes profiles have the extruder position instead of the ID of the extruder definition.
+ if "metadata" in parser and "extruder" in parser["metadata"]: #Only do this for extruder profiles.
+ extruder_id = parser["metadata"]["extruder"]
+ if extruder_id in _EXTRUDER_TO_POSITION:
+ extruder_position = _EXTRUDER_TO_POSITION[extruder_id]
+ else:
+ extruder_position = 0 #The user was using custom extruder definitions. He's on his own then.
+
+ parser["metadata"]["position"] = str(extruder_position)
+ del parser["metadata"]["extruder"]
+
+ quality_type = parser["metadata"]["quality_type"]
+ parser["metadata"]["quality_type"] = quality_type.lower()
+
+ #Update version number.
+ parser["general"]["version"] = "3"
+
+ result = io.StringIO()
+ parser.write(result)
+ return [filename], [result.getvalue()]
\ No newline at end of file
diff --git a/plugins/VersionUpgrade/VersionUpgrade32to33/__init__.py b/plugins/VersionUpgrade/VersionUpgrade32to33/__init__.py
new file mode 100644
index 0000000000..72ff6e1de9
--- /dev/null
+++ b/plugins/VersionUpgrade/VersionUpgrade32to33/__init__.py
@@ -0,0 +1,44 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from . import VersionUpgrade32to33
+
+upgrade = VersionUpgrade32to33.VersionUpgrade32to33()
+
+def getMetaData():
+ return {
+ "version_upgrade": {
+ # From To Upgrade function
+ ("machine_stack", 3000004): ("machine_stack", 4000004, upgrade.upgradeStack),
+ ("extruder_train", 3000004): ("extruder_train", 4000004, upgrade.upgradeStack),
+
+ ("definition_changes", 2000004): ("definition_changes", 3000004, upgrade.upgradeInstanceContainer),
+ ("quality_changes", 2000004): ("quality_changes", 3000004, upgrade.upgradeQualityChanges),
+ ("user", 2000004): ("user", 3000004, upgrade.upgradeInstanceContainer)
+ },
+ "sources": {
+ "machine_stack": {
+ "get_version": upgrade.getCfgVersion,
+ "location": {"./machine_instances"}
+ },
+ "extruder_train": {
+ "get_version": upgrade.getCfgVersion,
+ "location": {"./extruders"}
+ },
+ "definition_changes": {
+ "get_version": upgrade.getCfgVersion,
+ "location": {"./definition_changes"}
+ },
+ "quality_changes": {
+ "get_version": upgrade.getCfgVersion,
+ "location": {"./quality"}
+ },
+ "user": {
+ "get_version": upgrade.getCfgVersion,
+ "location": {"./user"}
+ }
+ }
+ }
+
+def register(app):
+ return { "version_upgrade": upgrade }
\ No newline at end of file
diff --git a/plugins/VersionUpgrade/VersionUpgrade32to33/plugin.json b/plugins/VersionUpgrade/VersionUpgrade32to33/plugin.json
new file mode 100644
index 0000000000..fbce09c807
--- /dev/null
+++ b/plugins/VersionUpgrade/VersionUpgrade32to33/plugin.json
@@ -0,0 +1,8 @@
+ {
+ "name": "Version Upgrade 3.2 to 3.3",
+ "author": "Ultimaker B.V.",
+ "version": "1.0.0",
+ "description": "Upgrades configurations from Cura 3.2 to Cura 3.3.",
+ "api": 4,
+ "i18n-catalog": "cura"
+}
diff --git a/plugins/XRayView/XRayPass.py b/plugins/XRayView/XRayPass.py
index 38c88a256e..a75d393b35 100644
--- a/plugins/XRayView/XRayPass.py
+++ b/plugins/XRayView/XRayPass.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2015 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import os.path
@@ -10,7 +10,7 @@ from UM.View.RenderPass import RenderPass
from UM.View.RenderBatch import RenderBatch
from UM.View.GL.OpenGL import OpenGL
-from UM.Scene.SceneNode import SceneNode
+from cura.Scene.CuraSceneNode import CuraSceneNode
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
class XRayPass(RenderPass):
@@ -27,7 +27,7 @@ class XRayPass(RenderPass):
batch = RenderBatch(self._shader, type = RenderBatch.RenderType.NoType, backface_cull = False, blend_mode = RenderBatch.BlendMode.Additive)
for node in DepthFirstIterator(self._scene.getRoot()):
- if type(node) is SceneNode and node.getMeshData() and node.isVisible():
+ if isinstance(node, CuraSceneNode) and node.getMeshData() and node.isVisible():
batch.addItem(node.getWorldTransformation(), node.getMeshData())
self.bind()
diff --git a/plugins/XRayView/xray_composite.shader b/plugins/XRayView/xray_composite.shader
index 82dca52cf9..0a8f6364d7 100644
--- a/plugins/XRayView/xray_composite.shader
+++ b/plugins/XRayView/xray_composite.shader
@@ -13,9 +13,9 @@ vertex =
}
fragment =
- uniform sampler2D u_layer0;
- uniform sampler2D u_layer1;
- uniform sampler2D u_layer2;
+ uniform sampler2D u_layer0; //Default pass.
+ uniform sampler2D u_layer1; //Selection pass.
+ uniform sampler2D u_layer2; //X-ray pass.
uniform vec2 u_offset[9];
@@ -83,9 +83,9 @@ vertex41core =
fragment41core =
#version 410
- uniform sampler2D u_layer0;
- uniform sampler2D u_layer1;
- uniform sampler2D u_layer2;
+ uniform sampler2D u_layer0; //Default pass.
+ uniform sampler2D u_layer1; //Selection pass.
+ uniform sampler2D u_layer2; //X-ray pass.
uniform vec2 u_offset[9];
diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py
index c580fd4458..5f4f765f77 100644
--- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py
+++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py
@@ -201,31 +201,34 @@ class XmlMaterialProfile(InstanceContainer):
## Begin Settings Block
builder.start("settings")
- if self.getDefinition().getId() == "fdmprinter":
+ if self.getMetaDataEntry("definition") == "fdmprinter":
for instance in self.findInstances():
self._addSettingElement(builder, instance)
machine_container_map = {}
- machine_nozzle_map = {}
+ machine_variant_map = {}
- variant_manager = CuraApplication.getInstance()._variant_manager
+ variant_manager = CuraApplication.getInstance().getVariantManager()
+
+ root_material_id = self.getMetaDataEntry("base_file") # if basefile is self.getId, this is a basefile.
+ all_containers = registry.findInstanceContainers(base_file = root_material_id)
- all_containers = registry.findInstanceContainers(GUID = self.getMetaDataEntry("GUID"), base_file = self.getId())
for container in all_containers:
- definition_id = container.getDefinition().getId()
+ definition_id = container.getMetaDataEntry("definition")
if definition_id == "fdmprinter":
continue
if definition_id not in machine_container_map:
machine_container_map[definition_id] = container
- if definition_id not in machine_nozzle_map:
- machine_nozzle_map[definition_id] = {}
+ if definition_id not in machine_variant_map:
+ machine_variant_map[definition_id] = {}
variant_name = container.getMetaDataEntry("variant_name")
if variant_name:
- machine_nozzle_map[definition_id][variant_name] = variant_manager.getVariantNode(definition_id,
- variant_name)
+ variant_dict = {"variant_node": variant_manager.getVariantNode(definition_id, variant_name),
+ "material_container": container}
+ machine_variant_map[definition_id][variant_name] = variant_dict
continue
machine_container_map[definition_id] = container
@@ -234,7 +237,8 @@ class XmlMaterialProfile(InstanceContainer):
product_id_map = self.getProductIdMap()
for definition_id, container in machine_container_map.items():
- definition = container.getDefinition()
+ definition_id = container.getMetaDataEntry("definition")
+ definition_metadata = registry.findDefinitionContainersMetadata(id = definition_id)[0]
product = definition_id
for product_name, product_id_list in product_id_map.items():
@@ -244,41 +248,74 @@ class XmlMaterialProfile(InstanceContainer):
builder.start("machine")
builder.start("machine_identifier", {
- "manufacturer": container.getMetaDataEntry("machine_manufacturer", definition.getMetaDataEntry("manufacturer", "Unknown")),
+ "manufacturer": container.getMetaDataEntry("machine_manufacturer",
+ definition_metadata.get("manufacturer", "Unknown")),
"product": product
})
builder.end("machine_identifier")
for instance in container.findInstances():
- if self.getDefinition().getId() == "fdmprinter" and self.getInstance(instance.definition.key) and self.getProperty(instance.definition.key, "value") == instance.value:
+ if self.getMetaDataEntry("definition") == "fdmprinter" and self.getInstance(instance.definition.key) and self.getProperty(instance.definition.key, "value") == instance.value:
# If the settings match that of the base profile, just skip since we inherit the base profile.
continue
self._addSettingElement(builder, instance)
# Find all hotend sub-profiles corresponding to this material and machine and add them to this profile.
- for hotend_name, variant_node in machine_nozzle_map[definition_id].items():
- # The hotend identifier is not the containers name, but its "name".
- builder.start("hotend", {"id": hotend_name})
+ buildplate_dict = {}
+ for variant_name, variant_dict in machine_variant_map[definition_id].items():
+ variant_type = variant_dict["variant_node"].metadata["hardware_type"]
+ from cura.Machines.VariantManager import VariantType
+ variant_type = VariantType(variant_type)
+ if variant_type == VariantType.NOZZLE:
+ # The hotend identifier is not the containers name, but its "name".
+ builder.start("hotend", {"id": variant_name})
- # Compatible is a special case, as it's added as a meta data entry (instead of an instance).
- compatible = variant_node.metadata.get("compatible")
- if compatible is not None:
- builder.start("setting", {"key": "hardware compatible"})
- if compatible:
- builder.data("yes")
- else:
- builder.data("no")
- builder.end("setting")
+ # Compatible is a special case, as it's added as a meta data entry (instead of an instance).
+ material_container = variant_dict["material_container"]
+ compatible = material_container.getMetaDataEntry("compatible")
+ if compatible is not None:
+ builder.start("setting", {"key": "hardware compatible"})
+ if compatible:
+ builder.data("yes")
+ else:
+ builder.data("no")
+ builder.end("setting")
- for instance in variant_node.getContainer().findInstances():
- if container.getInstance(instance.definition.key) and container.getProperty(instance.definition.key, "value") == instance.value:
- # If the settings match that of the machine profile, just skip since we inherit the machine profile.
- continue
+ for instance in material_container.findInstances():
+ if container.getInstance(instance.definition.key) and container.getProperty(instance.definition.key, "value") == instance.value:
+ # If the settings match that of the machine profile, just skip since we inherit the machine profile.
+ continue
- self._addSettingElement(builder, instance)
+ self._addSettingElement(builder, instance)
- builder.end("hotend")
+ if material_container.getMetaDataEntry("buildplate_compatible") and not buildplate_dict:
+ buildplate_dict["buildplate_compatible"] = material_container.getMetaDataEntry("buildplate_compatible")
+ buildplate_dict["buildplate_recommended"] = material_container.getMetaDataEntry("buildplate_recommended")
+ buildplate_dict["material_container"] = material_container
+
+ builder.end("hotend")
+
+ if buildplate_dict:
+ for variant_name in buildplate_dict["buildplate_compatible"]:
+ builder.start("buildplate", {"id": variant_name})
+
+ material_container = buildplate_dict["material_container"]
+ buildplate_compatible_dict = material_container.getMetaDataEntry("buildplate_compatible")
+ buildplate_recommended_dict = material_container.getMetaDataEntry("buildplate_recommended")
+ if buildplate_compatible_dict:
+ compatible = buildplate_compatible_dict[variant_name]
+ recommended = buildplate_recommended_dict[variant_name]
+
+ builder.start("setting", {"key": "hardware compatible"})
+ builder.data("yes" if compatible else "no")
+ builder.end("setting")
+
+ builder.start("setting", {"key": "hardware recommended"})
+ builder.data("yes" if recommended else "no")
+ builder.end("setting")
+
+ builder.end("buildplate")
builder.end("machine")
@@ -622,7 +659,8 @@ class XmlMaterialProfile(InstanceContainer):
from cura.Machines.VariantManager import VariantType
variant_manager = CuraApplication.getInstance().getVariantManager()
- variant_node = variant_manager.getVariantNode(machine_id, buildplate_id)
+ variant_node = variant_manager.getVariantNode(machine_id, buildplate_id,
+ variant_type = VariantType.BUILD_PLATE)
if not variant_node:
continue
@@ -815,15 +853,11 @@ class XmlMaterialProfile(InstanceContainer):
if machine_compatibility:
new_material_id = container_id + "_" + machine_id
- # The child or derived material container may already exist. This can happen when a material in a
- # project file and the a material in Cura have the same ID.
- # In the case if a derived material already exists, override that material container because if
- # the data in the parent material has been changed, the derived ones should be updated too.
- found_materials = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = new_material_id)
- if found_materials:
- new_material_metadata = found_materials[0]
- else:
- new_material_metadata = {}
+ # Do not look for existing container/container metadata with the same ID although they may exist.
+ # In project loading and perhaps some other places, we only want to get information (metadata)
+ # from a file without changing the current state of the system. If we overwrite the existing
+ # metadata here, deserializeMetadata() will not be safe for retrieving information.
+ new_material_metadata = {}
new_material_metadata.update(base_metadata)
new_material_metadata["id"] = new_material_id
@@ -831,8 +865,7 @@ class XmlMaterialProfile(InstanceContainer):
new_material_metadata["machine_manufacturer"] = machine_manufacturer
new_material_metadata["definition"] = machine_id
- if len(found_materials) == 0: #This is a new material.
- result_metadata.append(new_material_metadata)
+ result_metadata.append(new_material_metadata)
buildplates = machine.iterfind("./um:buildplate", cls.__namespaces)
buildplate_map = {}
@@ -843,15 +876,17 @@ class XmlMaterialProfile(InstanceContainer):
if buildplate_id is None:
continue
- variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = buildplate_id)
- if not variant_containers:
+ variant_metadata = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = buildplate_id)
+ if not variant_metadata:
# It is not really properly defined what "ID" is so also search for variants by name.
- variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(definition = machine_id, name = buildplate_id)
+ variant_metadata = ContainerRegistry.getInstance().findInstanceContainersMetadata(definition = machine_id, name = buildplate_id)
- if not variant_containers:
+ if not variant_metadata:
continue
settings = buildplate.iterfind("./um:setting", cls.__namespaces)
+ buildplate_compatibility = True
+ buildplate_recommended = True
for entry in settings:
key = entry.get("key")
if key == "hardware compatible":
@@ -859,8 +894,8 @@ class XmlMaterialProfile(InstanceContainer):
elif key == "hardware recommended":
buildplate_recommended = cls._parseCompatibleValue(entry.text)
- buildplate_map["buildplate_compatible"][buildplate_id] = buildplate_map["buildplate_compatible"]
- buildplate_map["buildplate_recommended"][buildplate_id] = buildplate_map["buildplate_recommended"]
+ buildplate_map["buildplate_compatible"][buildplate_id] = buildplate_compatibility
+ buildplate_map["buildplate_recommended"][buildplate_id] = buildplate_recommended
for hotend in machine.iterfind("./um:hotend", cls.__namespaces):
hotend_name = hotend.get("id")
@@ -875,12 +910,8 @@ class XmlMaterialProfile(InstanceContainer):
new_hotend_specific_material_id = container_id + "_" + machine_id + "_" + hotend_name.replace(" ", "_")
- # Same as machine compatibility, keep the derived material containers consistent with the parent material
- found_materials = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = new_hotend_specific_material_id)
- if found_materials:
- new_hotend_material_metadata = found_materials[0]
- else:
- new_hotend_material_metadata = {}
+ # Same as above, do not overwrite existing metadata.
+ new_hotend_material_metadata = {}
new_hotend_material_metadata.update(base_metadata)
new_hotend_material_metadata["variant_name"] = hotend_name
@@ -892,8 +923,7 @@ class XmlMaterialProfile(InstanceContainer):
new_hotend_material_metadata["buildplate_compatible"] = buildplate_map["buildplate_compatible"]
new_hotend_material_metadata["buildplate_recommended"] = buildplate_map["buildplate_recommended"]
- if len(found_materials) == 0:
- result_metadata.append(new_hotend_material_metadata)
+ result_metadata.append(new_hotend_material_metadata)
# there is only one ID for a machine. Once we have reached here, it means we have already found
# a workable ID for that machine, so there is no need to continue
diff --git a/resources/definitions/bq_hephestos_xl.def.json b/resources/definitions/bq_hephestos_xl.def.json
index 75b756c71e..08be4b8d34 100644
--- a/resources/definitions/bq_hephestos_xl.def.json
+++ b/resources/definitions/bq_hephestos_xl.def.json
@@ -6,7 +6,7 @@
"visible": true,
"manufacturer": "BQ",
"author": "BQ",
- "file_formats": "text/x-code",
+ "file_formats": "text/x-gcode",
"platform": "bq_hephestos_platform.stl",
"platform_offset": [ 0, -82, 0]
},
diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json
index 8c67462667..21ee543333 100644
--- a/resources/definitions/fdmprinter.def.json
+++ b/resources/definitions/fdmprinter.def.json
@@ -211,6 +211,18 @@
"settable_per_extruder": false,
"settable_per_meshgroup": false
},
+ "extruders_enabled_count":
+ {
+ "label": "Number of Extruders that are enabled",
+ "description": "Number of extruder trains that are enabled; automatically set in software",
+ "value": "machine_extruder_count",
+ "minimum_value": "1",
+ "maximum_value": "16",
+ "type": "int",
+ "settable_per_mesh": false,
+ "settable_per_extruder": false,
+ "settable_per_meshgroup": false
+ },
"machine_nozzle_tip_outer_diameter":
{
"label": "Outer nozzle diameter",
@@ -620,6 +632,73 @@
"settable_per_extruder": false,
"settable_per_meshgroup": false
},
+ "machine_steps_per_mm_x":
+ {
+ "label": "Steps per Millimeter (X)",
+ "description": "How many steps of the stepper motor will result in one millimeter of movement in the X direction.",
+ "type": "int",
+ "default_value": 50,
+ "minimum_value": "0.0000001",
+ "settable_per_mesh": false,
+ "settable_per_extruder": true
+ },
+ "machine_steps_per_mm_y":
+ {
+ "label": "Steps per Millimeter (Y)",
+ "description": "How many steps of the stepper motor will result in one millimeter of movement in the Y direction.",
+ "type": "int",
+ "default_value": 50,
+ "minimum_value": "0.0000001",
+ "settable_per_mesh": false,
+ "settable_per_extruder": true
+ },
+ "machine_steps_per_mm_z":
+ {
+ "label": "Steps per Millimeter (Z)",
+ "description": "How many steps of the stepper motor will result in one millimeter of movement in the Z direction.",
+ "type": "int",
+ "default_value": 50,
+ "minimum_value": "0.0000001",
+ "settable_per_mesh": false,
+ "settable_per_extruder": true
+ },
+ "machine_steps_per_mm_e":
+ {
+ "label": "Steps per Millimeter (E)",
+ "description": "How many steps of the stepper motors will result in one millimeter of extrusion.",
+ "type": "int",
+ "default_value": 1600,
+ "minimum_value": "0.0000001",
+ "settable_per_mesh": false,
+ "settable_per_extruder": true
+ },
+ "machine_endstop_positive_direction_x":
+ {
+ "label": "X Endstop in Positive Direction",
+ "description": "Whether the endstop of the X axis is in the positive direction (high X coordinate) or negative (low X coordinate).",
+ "type": "bool",
+ "default_value": false,
+ "settable_per_mesh": false,
+ "settable_per_extruder": true
+ },
+ "machine_endstop_positive_direction_y":
+ {
+ "label": "Y Endstop in Positive Direction",
+ "description": "Whether the endstop of the Y axis is in the positive direction (high Y coordinate) or negative (low Y coordinate).",
+ "type": "bool",
+ "default_value": false,
+ "settable_per_mesh": false,
+ "settable_per_extruder": true
+ },
+ "machine_endstop_positive_direction_z":
+ {
+ "label": "Z Endstop in Positive Direction",
+ "description": "Whether the endstop of the Z axis is in the positive direction (high Z coordinate) or negative (low Z coordinate).",
+ "type": "bool",
+ "default_value": true,
+ "settable_per_mesh": false,
+ "settable_per_extruder": true
+ },
"machine_minimum_feedrate":
{
"label": "Minimum Feedrate",
@@ -630,6 +709,16 @@
"settable_per_mesh": false,
"settable_per_extruder": false,
"settable_per_meshgroup": false
+ },
+ "machine_feeder_wheel_diameter":
+ {
+ "label": "Feeder Wheel Diameter",
+ "description": "The diameter of the wheel that drives the material in the feeder.",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 10.0,
+ "settable_per_mesh": false,
+ "settable_per_extruder": true
}
}
},
@@ -887,7 +976,7 @@
"settable_per_extruder": false,
"settable_per_meshgroup": true,
"settable_globally": true,
- "enabled": "machine_extruder_count > 1",
+ "enabled": "extruders_enabled_count > 1",
"children": {
"wall_0_extruder_nr":
{
@@ -900,7 +989,7 @@
"settable_per_extruder": false,
"settable_per_meshgroup": true,
"settable_globally": true,
- "enabled": "machine_extruder_count > 1"
+ "enabled": "extruders_enabled_count > 1"
},
"wall_x_extruder_nr":
{
@@ -913,7 +1002,7 @@
"settable_per_extruder": false,
"settable_per_meshgroup": true,
"settable_globally": true,
- "enabled": "machine_extruder_count > 1"
+ "enabled": "extruders_enabled_count > 1"
}
}
},
@@ -970,7 +1059,7 @@
"settable_per_extruder": false,
"settable_per_meshgroup": true,
"settable_globally": true,
- "enabled": "machine_extruder_count > 1 and max(extruderValues('roofing_layer_count')) > 0 and max(extruderValues('top_layers')) > 0"
+ "enabled": "extruders_enabled_count > 1 and max(extruderValues('roofing_layer_count')) > 0 and max(extruderValues('top_layers')) > 0"
},
"roofing_layer_count":
{
@@ -995,7 +1084,7 @@
"settable_per_extruder": false,
"settable_per_meshgroup": true,
"settable_globally": true,
- "enabled": "machine_extruder_count > 1"
+ "enabled": "extruders_enabled_count > 1"
},
"top_bottom_thickness":
{
@@ -1465,7 +1554,7 @@
"settable_per_extruder": false,
"settable_per_meshgroup": true,
"settable_globally": true,
- "enabled": "machine_extruder_count > 1"
+ "enabled": "extruders_enabled_count > 1"
},
"infill_sparse_density":
{
@@ -1916,7 +2005,7 @@
"minimum_value": "0",
"maximum_value_warning": "10.0",
"maximum_value": "machine_nozzle_heat_up_speed",
- "enabled": "material_flow_dependent_temperature or (machine_extruder_count > 1 and material_final_print_temperature != material_print_temperature)",
+ "enabled": "material_flow_dependent_temperature or (extruders_enabled_count > 1 and material_final_print_temperature != material_print_temperature)",
"settable_per_mesh": false,
"settable_per_extruder": true
},
@@ -2179,7 +2268,7 @@
"minimum_value": "-273.15",
"minimum_value_warning": "0",
"maximum_value_warning": "260",
- "enabled": "machine_extruder_count > 1 and machine_nozzle_temp_enabled",
+ "enabled": "extruders_enabled_count > 1 and machine_nozzle_temp_enabled",
"settable_per_mesh": false,
"settable_per_extruder": true
},
@@ -3281,7 +3370,7 @@
"description": "After the machine switched from one extruder to the other, the build plate is lowered to create clearance between the nozzle and the print. This prevents the nozzle from leaving oozed material on the outside of a print.",
"type": "bool",
"default_value": true,
- "enabled": "retraction_hop_enabled and machine_extruder_count > 1",
+ "enabled": "retraction_hop_enabled and extruders_enabled_count > 1",
"settable_per_mesh": false,
"settable_per_extruder": true
}
@@ -3459,7 +3548,8 @@
"description": "The extruder train to use for printing the support. This is used in multi-extrusion.",
"type": "extruder",
"default_value": "0",
- "enabled": "(support_enable or support_tree_enable) and machine_extruder_count > 1",
+ "value": "-1",
+ "enabled": "(support_enable or support_tree_enable) and extruders_enabled_count > 1",
"settable_per_mesh": false,
"settable_per_extruder": false,
"children": {
@@ -3470,7 +3560,7 @@
"type": "extruder",
"default_value": "0",
"value": "support_extruder_nr",
- "enabled": "(support_enable or support_tree_enable) and machine_extruder_count > 1",
+ "enabled": "(support_enable or support_tree_enable) and extruders_enabled_count > 1",
"settable_per_mesh": false,
"settable_per_extruder": false
},
@@ -3481,7 +3571,7 @@
"type": "extruder",
"default_value": "0",
"value": "support_extruder_nr",
- "enabled": "(support_enable or support_tree_enable) and machine_extruder_count > 1",
+ "enabled": "(support_enable or support_tree_enable) and extruders_enabled_count > 1",
"settable_per_mesh": false,
"settable_per_extruder": false
},
@@ -3492,7 +3582,7 @@
"type": "extruder",
"default_value": "0",
"value": "support_extruder_nr",
- "enabled": "(support_enable or support_tree_enable) and machine_extruder_count > 1",
+ "enabled": "(support_enable or support_tree_enable) and extruders_enabled_count > 1",
"settable_per_mesh": false,
"settable_per_extruder": false,
"children":
@@ -3504,7 +3594,7 @@
"type": "extruder",
"default_value": "0",
"value": "support_interface_extruder_nr",
- "enabled": "(support_enable or support_tree_enable) and machine_extruder_count > 1",
+ "enabled": "(support_enable or support_tree_enable) and extruders_enabled_count > 1",
"settable_per_mesh": false,
"settable_per_extruder": false
},
@@ -3515,7 +3605,7 @@
"type": "extruder",
"default_value": "0",
"value": "support_interface_extruder_nr",
- "enabled": "(support_enable or support_tree_enable) and machine_extruder_count > 1",
+ "enabled": "(support_enable or support_tree_enable) and extruders_enabled_count > 1",
"settable_per_mesh": false,
"settable_per_extruder": false
}
@@ -4185,7 +4275,8 @@
"description": "The extruder train to use for printing the skirt/brim/raft. This is used in multi-extrusion.",
"type": "extruder",
"default_value": "0",
- "enabled": "machine_extruder_count > 1 and resolveOrValue('adhesion_type') != 'none'",
+ "value": "-1",
+ "enabled": "extruders_enabled_count > 1 and resolveOrValue('adhesion_type') != 'none'",
"settable_per_mesh": false,
"settable_per_extruder": false
},
@@ -4756,7 +4847,7 @@
"label": "Enable Prime Tower",
"description": "Print a tower next to the print which serves to prime the material after each nozzle switch.",
"type": "bool",
- "enabled": "machine_extruder_count > 1",
+ "enabled": "extruders_enabled_count > 1",
"default_value": false,
"resolve": "any(extruderValues('prime_tower_enable'))",
"settable_per_mesh": false,
@@ -4904,7 +4995,7 @@
"description": "Enable exterior ooze shield. This will create a shell around the model which is likely to wipe a second nozzle if it's at the same height as the first nozzle.",
"type": "bool",
"resolve": "any(extruderValues('ooze_shield_enabled'))",
- "enabled": "machine_extruder_count > 1",
+ "enabled": "extruders_enabled_count > 1",
"default_value": false,
"settable_per_mesh": false,
"settable_per_extruder": false
@@ -4997,7 +5088,7 @@
"description": "Remove areas where multiple meshes are overlapping with each other. This may be used if merged dual material objects overlap with each other.",
"type": "bool",
"default_value": true,
- "value": "machine_extruder_count > 1",
+ "value": "extruders_enabled_count > 1",
"settable_per_mesh": false,
"settable_per_extruder": false,
"settable_per_meshgroup": true
@@ -5044,7 +5135,7 @@
"one_at_a_time": "One at a Time"
},
"default_value": "all_at_once",
- "enabled": "machine_extruder_count == 1",
+ "enabled": "extruders_enabled_count == 1",
"settable_per_mesh": false,
"settable_per_extruder": false,
"settable_per_meshgroup": false
@@ -6229,6 +6320,258 @@
"settable_per_mesh": false,
"settable_per_extruder": false,
"settable_per_meshgroup": false
+ },
+ "bridge_settings_enabled":
+ {
+ "label": "Enable Bridge Settings",
+ "description": "Detect bridges and modify print speed, flow and fan settings while bridges are printed.",
+ "type": "bool",
+ "default_value": false,
+ "settable_per_mesh": true,
+ "settable_per_extruder": false,
+ "settable_per_meshgroup": false
+ },
+ "bridge_wall_min_length":
+ {
+ "label": "Minimum Bridge Wall Length",
+ "description": "Unsupported walls shorter than this will be printed using the normal wall settings. Longer unsupported walls will be printed using the bridge wall settings.",
+ "unit": "mm",
+ "type": "float",
+ "minimum_value": "0",
+ "default_value": 5,
+ "enabled": "bridge_settings_enabled",
+ "settable_per_mesh": false,
+ "settable_per_extruder": true
+ },
+ "bridge_skin_support_threshold":
+ {
+ "label": "Bridge Skin Support Threshold",
+ "description": "If a skin region is supported for less than this percentage of its area, print it using the bridge settings. Otherwise it is printed using the normal skin settings.",
+ "unit": "%",
+ "default_value": 50,
+ "type": "float",
+ "minimum_value": "0",
+ "maximum_value": "100",
+ "enabled": "bridge_settings_enabled",
+ "settable_per_mesh": true
+ },
+ "bridge_wall_max_overhang":
+ {
+ "label": "Bridge Wall Max Overhang",
+ "description": "The maximum allowed width of the region of air below a wall line before the wall is printed using bridge settings. Expressed as a percentage of the wall line width. When the air gap is wider than this, the wall line is printed using the bridge settings. Otherwise, the wall line is printed using the normal settings. The lower the value, the more likely it is that overhung wall lines will be printed using bridge settings.",
+ "unit": "%",
+ "default_value": 100,
+ "type": "float",
+ "minimum_value": "0",
+ "maximum_value": "100",
+ "enabled": "bridge_settings_enabled",
+ "settable_per_mesh": true
+ },
+ "bridge_wall_coast":
+ {
+ "label": "Bridge Wall Coasting",
+ "description": "This controls the distance the extruder should coast immediately before a bridge wall begins. Coasting before the bridge starts can reduce the pressure in the nozzle and may produce a flatter bridge.",
+ "unit": "%",
+ "default_value": 100,
+ "type": "float",
+ "minimum_value": "0",
+ "maximum_value": "500",
+ "enabled": "bridge_settings_enabled",
+ "settable_per_mesh": false
+ },
+ "bridge_wall_speed":
+ {
+ "label": "Bridge Wall Speed",
+ "description": "The speed at which the bridge walls are printed.",
+ "unit": "mm/s",
+ "type": "float",
+ "minimum_value": "cool_min_speed",
+ "maximum_value": "math.sqrt(machine_max_feedrate_x ** 2 + machine_max_feedrate_y ** 2)",
+ "maximum_value_warning": "300",
+ "default_value": 15,
+ "value": "max(cool_min_speed, speed_wall_0 / 2)",
+ "enabled": "bridge_settings_enabled",
+ "settable_per_mesh": true
+ },
+ "bridge_wall_material_flow":
+ {
+ "label": "Bridge Wall Flow",
+ "description": "When printing bridge walls, the amount of material extruded is multiplied by this value.",
+ "unit": "%",
+ "default_value": 50,
+ "type": "float",
+ "minimum_value": "5",
+ "minimum_value_warning": "50",
+ "maximum_value_warning": "150",
+ "enabled": "bridge_settings_enabled",
+ "settable_per_mesh": true
+ },
+ "bridge_skin_speed":
+ {
+ "label": "Bridge Skin Speed",
+ "description": "The speed at which bridge skin regions are printed.",
+ "unit": "mm/s",
+ "type": "float",
+ "minimum_value": "cool_min_speed",
+ "maximum_value": "math.sqrt(machine_max_feedrate_x ** 2 + machine_max_feedrate_y ** 2)",
+ "maximum_value_warning": "300",
+ "default_value": 15,
+ "value": "max(cool_min_speed, speed_topbottom / 2)",
+ "enabled": "bridge_settings_enabled",
+ "settable_per_mesh": true
+ },
+ "bridge_skin_material_flow":
+ {
+ "label": "Bridge Skin Flow",
+ "description": "When printing bridge skin regions, the amount of material extruded is multiplied by this value.",
+ "unit": "%",
+ "default_value": 60,
+ "type": "float",
+ "minimum_value": "5",
+ "minimum_value_warning": "50",
+ "maximum_value_warning": "150",
+ "enabled": "bridge_settings_enabled",
+ "settable_per_mesh": true
+ },
+ "bridge_skin_density":
+ {
+ "label": "Bridge Skin Density",
+ "description": "The density of the bridge skin layer. Values less than 100 will increase the gaps between the skin lines.",
+ "unit": "%",
+ "default_value": 100,
+ "type": "float",
+ "minimum_value": "5",
+ "maximum_value": "100",
+ "minimum_value_warning": "20",
+ "enabled": "bridge_settings_enabled",
+ "settable_per_mesh": true
+ },
+ "bridge_fan_speed":
+ {
+ "label": "Bridge Fan Speed",
+ "description": "Percentage fan speed to use when printing bridge walls and skin.",
+ "unit": "%",
+ "minimum_value": "0",
+ "maximum_value": "100",
+ "default_value": 100,
+ "type": "float",
+ "enabled": "bridge_settings_enabled",
+ "settable_per_mesh": true
+ },
+ "bridge_enable_more_layers":
+ {
+ "label": "Bridge Has Multiple Layers",
+ "description": "If enabled, the second and third layers above the air are printed using the following settings. Otherwise, those layers are printed using the normal settings.",
+ "type": "bool",
+ "default_value": true,
+ "enabled": "bridge_settings_enabled",
+ "settable_per_mesh": true
+ },
+ "bridge_skin_speed_2":
+ {
+ "label": "Bridge Second Skin Speed",
+ "description": "Print speed to use when printing the second bridge skin layer.",
+ "unit": "mm/s",
+ "type": "float",
+ "minimum_value": "cool_min_speed",
+ "maximum_value": "math.sqrt(machine_max_feedrate_x ** 2 + machine_max_feedrate_y ** 2)",
+ "maximum_value_warning": "300",
+ "default_value": 25,
+ "value": "bridge_skin_speed",
+ "enabled": "bridge_settings_enabled and bridge_enable_more_layers",
+ "settable_per_mesh": true
+ },
+ "bridge_skin_material_flow_2":
+ {
+ "label": "Bridge Second Skin Flow",
+ "description": "When printing the second bridge skin layer, the amount of material extruded is multiplied by this value.",
+ "unit": "%",
+ "default_value": 100,
+ "type": "float",
+ "minimum_value": "5",
+ "maximum_value": "500",
+ "minimum_value_warning": "50",
+ "maximum_value_warning": "150",
+ "enabled": "bridge_settings_enabled and bridge_enable_more_layers",
+ "settable_per_mesh": true
+ },
+ "bridge_skin_density_2":
+ {
+ "label": "Bridge Second Skin Density",
+ "description": "The density of the second bridge skin layer. Values less than 100 will increase the gaps between the skin lines.",
+ "unit": "%",
+ "default_value": 75,
+ "type": "float",
+ "minimum_value": "5",
+ "maximum_value": "100",
+ "minimum_value_warning": "20",
+ "enabled": "bridge_settings_enabled and bridge_enable_more_layers",
+ "settable_per_mesh": true
+ },
+ "bridge_fan_speed_2":
+ {
+ "label": "Bridge Second Skin Fan Speed",
+ "description": "Percentage fan speed to use when printing the second bridge skin layer.",
+ "unit": "%",
+ "minimum_value": "0",
+ "maximum_value": "100",
+ "default_value": 0,
+ "type": "float",
+ "enabled": "bridge_settings_enabled and bridge_enable_more_layers",
+ "settable_per_mesh": true
+ },
+ "bridge_skin_speed_3":
+ {
+ "label": "Bridge Third Skin Speed",
+ "description": "Print speed to use when printing the third bridge skin layer.",
+ "unit": "mm/s",
+ "type": "float",
+ "minimum_value": "cool_min_speed",
+ "maximum_value": "math.sqrt(machine_max_feedrate_x ** 2 + machine_max_feedrate_y ** 2)",
+ "maximum_value_warning": "300",
+ "default_value": 15,
+ "value": "bridge_skin_speed",
+ "enabled": "bridge_settings_enabled and bridge_enable_more_layers",
+ "settable_per_mesh": true
+ },
+ "bridge_skin_material_flow_3":
+ {
+ "label": "Bridge Third Skin Flow",
+ "description": "When printing the third bridge skin layer, the amount of material extruded is multiplied by this value.",
+ "unit": "%",
+ "default_value": 110,
+ "type": "float",
+ "minimum_value": "5",
+ "maximum_value": "500",
+ "minimum_value_warning": "50",
+ "maximum_value_warning": "150",
+ "enabled": "bridge_settings_enabled and bridge_enable_more_layers",
+ "settable_per_mesh": true
+ },
+ "bridge_skin_density_3":
+ {
+ "label": "Bridge Third Skin Density",
+ "description": "The density of the third bridge skin layer. Values less than 100 will increase the gaps between the skin lines.",
+ "unit": "%",
+ "default_value": 80,
+ "type": "float",
+ "minimum_value": "5",
+ "maximum_value": "100",
+ "minimum_value_warning": "20",
+ "enabled": "bridge_settings_enabled and bridge_enable_more_layers",
+ "settable_per_mesh": true
+ },
+ "bridge_fan_speed_3":
+ {
+ "label": "Bridge Third Skin Fan Speed",
+ "description": "Percentage fan speed to use when printing the third bridge skin layer.",
+ "unit": "%",
+ "minimum_value": "0",
+ "maximum_value": "100",
+ "default_value": 0,
+ "type": "float",
+ "enabled": "bridge_settings_enabled and bridge_enable_more_layers",
+ "settable_per_mesh": true
}
}
},
diff --git a/resources/definitions/malyan_m180.def.json b/resources/definitions/malyan_m180.def.json
index 5e0a6038dd..11b61328ed 100644
--- a/resources/definitions/malyan_m180.def.json
+++ b/resources/definitions/malyan_m180.def.json
@@ -25,8 +25,7 @@
"default_value": true
},
"machine_nozzle_size": {
- "default_value": 0.4,
- "minimum_value": "0.001"
+ "default_value": 0.4
},
"machine_head_with_fans_polygon": {
"default_value": [
@@ -36,6 +35,21 @@
[ 18, 35 ]
]
},
+ "machine_max_feedrate_z": {
+ "default_value": 400
+ },
+ "machine_steps_per_mm_x": {
+ "default_value": 93
+ },
+ "machine_steps_per_mm_y": {
+ "default_value": 93
+ },
+ "machine_steps_per_mm_z": {
+ "default_value": 1600
+ },
+ "machine_steps_per_mm_e": {
+ "default_value": 92
+ },
"gantry_height": {
"default_value": 55
},
diff --git a/resources/definitions/malyan_m200.def.json b/resources/definitions/malyan_m200.def.json
index a3f4f81ecf..ec3237a7e6 100644
--- a/resources/definitions/malyan_m200.def.json
+++ b/resources/definitions/malyan_m200.def.json
@@ -30,7 +30,12 @@
"speed_infill": { "value": "speed_print" },
"speed_topbottom": {"value": "speed_print / 2"},
- "layer_height": { "minimum_value": "0.04375", "maximum_value": "machine_nozzle_size * 0.875", "maximum_value_warning": "machine_nozzle_size * 0.48125 + 0.0875", "default_value": 0.13125 },
+ "layer_height":
+ {
+ "minimum_value_warning": "0.04375",
+ "maximum_value_warning": "machine_nozzle_size * 0.48125 + 0.0875",
+ "default_value": 0.13125
+ },
"line_width": { "value": "round(machine_nozzle_size * 0.875, 2)" },
"material_print_temperature": { "minimum_value": "0" },
diff --git a/resources/definitions/printrbot_simple_makers_kit.def.json b/resources/definitions/printrbot_simple_makers_kit.def.json
new file mode 100644
index 0000000000..e2afd57826
--- /dev/null
+++ b/resources/definitions/printrbot_simple_makers_kit.def.json
@@ -0,0 +1,38 @@
+{
+ "version": 2,
+ "name": "Printrbot Simple Maker's Kit (1405)",
+ "inherits": "fdmprinter",
+ "metadata": {
+ "visible": true,
+ "author": "Timur Tabi",
+ "manufacturer": "Printrbot",
+ "file_formats": "text/x-gcode"
+ },
+
+ "overrides": {
+ "machine_name": { "default_value": "Printrbot Simple Maker's Kit (1405)" },
+ "machine_heated_bed": { "default_value": false },
+ "machine_width": { "default_value": 100 },
+ "machine_depth": { "default_value": 100 },
+ "machine_height": { "default_value": 115 },
+ "material_diameter": { "default_value": 1.75 },
+ "machine_nozzle_size": { "default_value": 0.4 },
+ "machine_head_with_fans_polygon": {
+ "default_value": [
+ [-40, 1000],
+ [-40, -10],
+ [60, 1000],
+ [60, -10]
+ ]
+ },
+ "gantry_height": { "default_value": 1000 },
+ "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
+
+ "machine_start_gcode": {
+ "default_value": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;home X/Y\nG28 Z0 ;home Z\nG92 E0 ;zero the extruded length\nG29 ;initiate auto bed leveling sequence"
+ },
+ "machine_end_gcode": {
+ "default_value": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nM106 S0 ;fan off\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit\nG1 Z+1 E-5 F9000 ;move Z up a bit and retract even more\nG28 X0 Y0 ;home X/Y, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning"
+ }
+ }
+}
diff --git a/resources/definitions/seemecnc_artemis.def.json b/resources/definitions/seemecnc_artemis.def.json
index 88a1b28de6..0b31abfa41 100644
--- a/resources/definitions/seemecnc_artemis.def.json
+++ b/resources/definitions/seemecnc_artemis.def.json
@@ -25,6 +25,7 @@
"machine_nozzle_size": { "default_value": 0.5 },
"machine_shape": { "default_value": "elliptic" },
"machine_width": { "default_value": 290 },
+ "material_diameter": { "default_value": 1.75 },
"relative_extrusion": { "default_value": false },
"retraction_amount": { "default_value": 3.2 },
"retraction_combing": { "default_value": "off" },
diff --git a/resources/definitions/seemecnc_v32.def.json b/resources/definitions/seemecnc_v32.def.json
index 5932403bc5..3f46c1540a 100644
--- a/resources/definitions/seemecnc_v32.def.json
+++ b/resources/definitions/seemecnc_v32.def.json
@@ -25,6 +25,7 @@
"machine_nozzle_size": { "default_value": 0.5 },
"machine_shape": { "default_value": "elliptic" },
"machine_width": { "default_value": 265 },
+ "material_diameter": { "default_value": 1.75 },
"relative_extrusion": { "default_value": false },
"retraction_amount": { "default_value": 3.2 },
"retraction_combing": { "default_value": "off" },
diff --git a/resources/definitions/tevo_tarantula.def.json b/resources/definitions/tevo_tarantula.def.json
index a9f9cefff2..c3bfb38192 100644
--- a/resources/definitions/tevo_tarantula.def.json
+++ b/resources/definitions/tevo_tarantula.def.json
@@ -2,7 +2,8 @@
"version": 2,
"name": "Tevo Tarantula",
"inherits": "fdmprinter",
- "metadata": {
+ "metadata":
+ {
"visible": true,
"author": "TheAssassin",
"manufacturer": "Tevo",
@@ -11,62 +12,39 @@
"platform": "prusai3_platform.stl"
},
- "overrides": {
- "machine_name": {
- "default_value": "Tevo Tarantula"
- },
- "machine_heated_bed": {
- "default_value": true
- },
- "machine_width": {
- "default_value": 200
- },
- "machine_height": {
- "default_value": 200
- },
- "machine_depth": {
- "default_value": 200
- },
- "machine_center_is_zero": {
- "default_value": false
- },
- "machine_nozzle_size": {
- "default_value": 0.4
- },
- "material_diameter": {
- "default_value": 1.75
- },
- "machine_head_polygon": {
- "default_value": [
+ "overrides":
+ {
+ "machine_name": { "default_value": "Tevo Tarantula" },
+ "machine_heated_bed": { "default_value": true },
+ "machine_width": { "default_value": 200 },
+ "machine_height": { "default_value": 200 },
+ "machine_depth": { "default_value": 200 },
+ "machine_center_is_zero": { "default_value": false },
+ "machine_nozzle_size": { "default_value": 0.4 },
+ "material_diameter": { "default_value": 1.75 },
+ "machine_head_polygon":
+ {
+ "default_value":
+ [
[-75, -18],
[-75, 35],
[18, 35],
[18, -18]
]
},
- "gantry_height": {
- "default_value": 55
- },
- "machine_gcode_flavor": {
- "default_value": "RepRap (Marlin/Sprinter)"
- },
- "machine_acceleration": {
- "default_value": 500
- },
- "machine_max_jerk_xy": {
- "default_value": 4.0
- },
- "machine_max_jerk_z": {
- "default_value": 0.2
- },
- "machine_max_jerk_e": {
- "default_value": 2.5
- },
- "machine_start_gcode": {
- "default_value": "G21 ;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 min endstops\nG1 Z15.0 F9000 ;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 F9000\n;Put printing message on LCD screen\nM117 Printing..."
- },
- "machine_end_gcode": {
- "default_value": "M104 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 F9000 ;move Z up a bit and retract filament even more\nG90 ;absolute positioning\nG1 X0 Y200 F3600 ;move extruder out of the way by moving the baseplate to the front for easier access to printed object\nM84 ;steppers off"
- }
+ "gantry_height": { "default_value": 55 },
+ "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
+ "machine_acceleration": { "default_value": 2650 },
+ "machine_max_jerk_xy": { "default_value": 15.0 },
+ "machine_max_jerk_z": { "default_value": 0.4 },
+ "machine_max_jerk_e": { "default_value": 5 },
+ "machine_max_feedrate_x": { "default_value": 255 },
+ "machine_max_feedrate_y": { "default_value": 225 },
+ "machine_max_feedrate_z": { "default_value": 3 },
+ "machine_max_acceleration_x": { "default_value": 2620 },
+ "machine_max_acceleration_y": { "default_value": 2650 },
+ "acceleration_print": { "default_value": 2650 },
+ "machine_start_gcode": { "default_value": "G21 ;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 min endstops\nG1 Z15.0 F9000 ;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 F9000\n;Put printing message on LCD screen\nM117 Printing..." },
+ "machine_end_gcode": { "default_value": "M104 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 F9000 ;move Z up a bit and retract filament even more\nG90 ;absolute positioning\nG1 X0 Y200 F3600 ;move extruder out of the way by moving the baseplate to the front for easier access to printed object\nM84 ;steppers off" }
}
}
diff --git a/resources/definitions/ubuild-3d_mr_bot_280.def.json b/resources/definitions/ubuild-3d_mr_bot_280.def.json
index 7f9069370c..4febdcd350 100644
--- a/resources/definitions/ubuild-3d_mr_bot_280.def.json
+++ b/resources/definitions/ubuild-3d_mr_bot_280.def.json
@@ -11,7 +11,8 @@
"file_formats": "text/x-gcode",
"icon": "icon_uBuild-3D",
"platform": "mr_bot_280_platform.stl",
- "has_materials": true
+ "has_materials": true,
+ "preferred_quality_type": "draft"
},
"overrides": {
@@ -24,7 +25,6 @@
"material_diameter": { "default_value": 1.75 },
"material_bed_temperature": { "default_value": 70 },
"machine_nozzle_size": { "default_value": 0.4 },
- "layer_height": { "default_value": 0.2 },
"layer_height_0": { "default_value": 0.1 },
"retraction_amount": { "default_value": 2 },
"retraction_speed": { "default_value": 50 },
diff --git a/resources/definitions/ultimaker3.def.json b/resources/definitions/ultimaker3.def.json
index dcf6b167c0..05f74c6342 100644
--- a/resources/definitions/ultimaker3.def.json
+++ b/resources/definitions/ultimaker3.def.json
@@ -6,7 +6,7 @@
"author": "Ultimaker",
"manufacturer": "Ultimaker B.V.",
"visible": true,
- "file_formats": "text/x-gcode",
+ "file_formats": "application/gzip;text/x-gcode",
"platform": "ultimaker3_platform.obj",
"platform_texture": "Ultimaker3backplate.png",
"platform_offset": [0, 0, 0],
@@ -43,10 +43,10 @@
{
"default_value":
[
- [ -29, 6.1 ],
- [ -29, -33.9 ],
- [ 71, 6.1 ],
- [ 71, -33.9 ]
+ [ -41.9, -45.8 ],
+ [ -41.9, 33.9 ],
+ [ 59.9, 33.9 ],
+ [ 59.9, -45.8 ]
]
},
"machine_gcode_flavor": { "default_value": "Griffin" },
@@ -110,9 +110,9 @@
"material_bed_temperature": { "maximum_value": "115" },
"material_bed_temperature_layer_0": { "maximum_value": "115" },
"material_standby_temperature": { "value": "100" },
- "meshfix_maximum_resolution": { "value": "0.04" },
+ "meshfix_maximum_resolution": { "value": "0.04" },
"multiple_mesh_overlap": { "value": "0" },
- "optimize_wall_printing_order": { "value": "True" },
+ "optimize_wall_printing_order": { "value": "True" },
"prime_tower_enable": { "default_value": true },
"raft_airgap": { "value": "0" },
"raft_base_thickness": { "value": "0.3" },
@@ -126,7 +126,7 @@
"retraction_count_max": { "value": "10" },
"retraction_extrusion_window": { "value": "1" },
"retraction_hop": { "value": "2" },
- "retraction_hop_enabled": { "value": "True" },
+ "retraction_hop_enabled": { "value": "extruders_enabled_count > 1" },
"retraction_hop_only_when_collides": { "value": "True" },
"retraction_min_travel": { "value": "5" },
"retraction_prime_speed": { "value": "15" },
@@ -150,7 +150,7 @@
"switch_extruder_prime_speed": { "value": "15" },
"switch_extruder_retraction_amount": { "value": "8" },
"top_bottom_thickness": { "value": "1" },
- "travel_avoid_distance": { "value": "3" },
+ "travel_avoid_distance": { "value": "3 if extruders_enabled_count > 1 else machine_nozzle_tip_outer_diameter / 2 * 1.5" },
"wall_0_inset": { "value": "0" },
"wall_line_width_x": { "value": "round(wall_line_width * 0.3 / 0.35, 2)" },
"wall_thickness": { "value": "1" },
diff --git a/resources/definitions/ultimaker3_extended.def.json b/resources/definitions/ultimaker3_extended.def.json
index 3a1be3a303..1e6c322c73 100644
--- a/resources/definitions/ultimaker3_extended.def.json
+++ b/resources/definitions/ultimaker3_extended.def.json
@@ -7,7 +7,7 @@
"manufacturer": "Ultimaker B.V.",
"quality_definition": "ultimaker3",
"visible": true,
- "file_formats": "text/x-gcode",
+ "file_formats": "application/gzip;text/x-gcode",
"platform": "ultimaker3_platform.obj",
"platform_texture": "Ultimaker3Extendedbackplate.png",
"platform_offset": [0, 0, 0],
diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml
index a8066e01e4..c4ebb790e8 100644
--- a/resources/qml/Cura.qml
+++ b/resources/qml/Cura.qml
@@ -194,14 +194,32 @@ UM.MainWindow
NozzleMenu { title: Cura.MachineManager.activeDefinitionVariantsName; visible: Cura.MachineManager.hasVariants; extruderIndex: index }
MaterialMenu { title: catalog.i18nc("@title:menu", "&Material"); visible: Cura.MachineManager.hasMaterials; extruderIndex: index }
- MenuSeparator {
+ MenuSeparator
+ {
visible: Cura.MachineManager.hasVariants || Cura.MachineManager.hasMaterials
}
- MenuItem {
+ MenuItem
+ {
text: catalog.i18nc("@action:inmenu", "Set as Active Extruder")
- onTriggered: Cura.ExtruderManager.setActiveExtruderIndex(model.index)
+ onTriggered: Cura.MachineManager.setExtruderIndex(model.index)
}
+
+ MenuItem
+ {
+ text: catalog.i18nc("@action:inmenu", "Enable Extruder")
+ onTriggered: Cura.MachineManager.setExtruderEnabled(model.index, true)
+ visible: !Cura.MachineManager.getExtruder(model.index).isEnabled
+ }
+
+ MenuItem
+ {
+ text: catalog.i18nc("@action:inmenu", "Disable Extruder")
+ onTriggered: Cura.MachineManager.setExtruderEnabled(model.index, false)
+ visible: Cura.MachineManager.getExtruder(model.index).isEnabled
+ enabled: Cura.MachineManager.numberExtrudersEnabled > 1
+ }
+
}
onObjectAdded: settingsMenu.insertItem(index, object)
onObjectRemoved: settingsMenu.removeItem(object)
@@ -636,7 +654,10 @@ UM.MainWindow
{
preferences.visible = true;
preferences.setPage(1);
- preferences.getCurrentItem().scrollToSection(source.key);
+ if(source && source.key)
+ {
+ preferences.getCurrentItem().scrollToSection(source.key);
+ }
}
}
diff --git a/resources/qml/ExtruderButton.qml b/resources/qml/ExtruderButton.qml
index e4350e0a2c..2c1b80047e 100644
--- a/resources/qml/ExtruderButton.qml
+++ b/resources/qml/ExtruderButton.qml
@@ -19,7 +19,7 @@ Button
iconSource: UM.Theme.getIcon("extruder_button")
checked: Cura.ExtruderManager.selectedObjectExtruders.indexOf(extruder.id) != -1
- enabled: UM.Selection.hasSelection
+ enabled: UM.Selection.hasSelection && extruder.stack.isEnabled
property color customColor: base.hovered ? UM.Theme.getColor("button_hover") : UM.Theme.getColor("button");
diff --git a/resources/qml/JobSpecs.qml b/resources/qml/JobSpecs.qml
index 04e8ec397f..742e8d6765 100644
--- a/resources/qml/JobSpecs.qml
+++ b/resources/qml/JobSpecs.qml
@@ -13,7 +13,7 @@ Item {
id: base
property bool activity: CuraApplication.platformActivity
- property string fileBaseName: ""
+ property string fileBaseName: PrintInformation.baseName
UM.I18nCatalog { id: catalog; name:"cura"}
@@ -24,26 +24,17 @@ Item {
target: backgroundItem
onHasMesh:
{
- if (base.fileBaseName == "")
+ if (PrintInformation.baseName == "")
{
- base.fileBaseName = name;
+ PrintInformation.baseName = name;
}
}
}
onActivityChanged: {
- if (activity == true && base.fileBaseName == ''){
- //this only runs when you open a file from the terminal (or something that works the same way; for example when you drag a file on the icon in MacOS or use 'open with' on Windows)
- base.fileBaseName = PrintInformation.baseName; //get the fileBaseName from PrintInformation.py because this saves the filebase when the file is opened using the terminal (or something alike)
- PrintInformation.setBaseName(base.fileBaseName);
- }
- if (activity == true && base.fileBaseName != ''){
- //this runs in all other cases where there is a mesh on the buildplate (activity == true). It uses the fileBaseName from the hasMesh signal
- PrintInformation.setBaseName(base.fileBaseName);
- }
- if (activity == false){
+ if (activity == false) {
//When there is no mesh in the buildplate; the printJobTextField is set to an empty string so it doesn't set an empty string as a jobName (which is later used for saving the file)
- PrintInformation.setBaseName('')
+ PrintInformation.baseName = ''
}
}
diff --git a/resources/qml/MachineSelection.qml b/resources/qml/MachineSelection.qml
index e40731f3ca..d075486eb2 100644
--- a/resources/qml/MachineSelection.qml
+++ b/resources/qml/MachineSelection.qml
@@ -12,7 +12,11 @@ import "Menus"
ToolButton
{
- text: Cura.MachineManager.activeMachineName
+ id: base
+ property bool isNetworkPrinter: Cura.MachineManager.activeMachineNetworkKey != ""
+ property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0
+ property var printerStatus: Cura.MachineManager.printerOutputDevices.length != 0 ? "connected" : "disconnected"
+ text: isNetworkPrinter ? Cura.MachineManager.activeMachineNetworkGroupName : Cura.MachineManager.activeMachineName
tooltip: Cura.MachineManager.activeMachineName
@@ -22,16 +26,13 @@ ToolButton
{
color:
{
- if(control.pressed)
- {
+ if (control.pressed) {
return UM.Theme.getColor("sidebar_header_active");
}
- else if(control.hovered)
- {
+ else if (control.hovered) {
return UM.Theme.getColor("sidebar_header_hover");
}
- else
- {
+ else {
return UM.Theme.getColor("sidebar_header_bar");
}
}
@@ -50,18 +51,32 @@ ToolButton
color: UM.Theme.getColor("text_emphasis")
source: UM.Theme.getIcon("arrow_bottom")
}
+
+ PrinterStatusIcon
+ {
+ id: printerStatusIcon
+ visible: printerConnected || isNetworkPrinter
+ status: printerStatus
+ anchors
+ {
+ verticalCenter: parent.verticalCenter
+ left: parent.left
+ leftMargin: UM.Theme.getSize("sidebar_margin").width
+ }
+ }
+
Label
{
id: sidebarComboBoxLabel
color: UM.Theme.getColor("sidebar_header_text_active")
text: control.text;
elide: Text.ElideRight;
- anchors.left: parent.left;
- anchors.leftMargin: UM.Theme.getSize("default_margin").width * 2
+ anchors.left: printerStatusIcon.visible ? printerStatusIcon.right : parent.left;
+ anchors.leftMargin: printerStatusIcon.visible ? UM.Theme.getSize("sidebar_lining").width : UM.Theme.getSize("sidebar_margin").width
anchors.right: downArrow.left;
anchors.rightMargin: control.rightMargin;
anchors.verticalCenter: parent.verticalCenter;
- font: UM.Theme.getFont("large")
+ font: UM.Theme.getFont("medium_bold")
}
}
label: Label {}
diff --git a/resources/qml/Menus/ConfigurationMenu/ConfigurationItem.qml b/resources/qml/Menus/ConfigurationMenu/ConfigurationItem.qml
new file mode 100644
index 0000000000..be8c8bcb45
--- /dev/null
+++ b/resources/qml/Menus/ConfigurationMenu/ConfigurationItem.qml
@@ -0,0 +1,124 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.7
+import QtQuick.Controls 2.0
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+Rectangle
+{
+ id: configurationItem
+
+ property var configuration: null
+ property var selected: false
+ signal activateConfiguration()
+
+ height: childrenRect.height
+ border.width: UM.Theme.getSize("default_lining").width
+ border.color: updateBorderColor()
+ color: selected ? UM.Theme.getColor("configuration_item_active") : UM.Theme.getColor("configuration_item")
+ property var textColor: selected ? UM.Theme.getColor("configuration_item_text_active") : UM.Theme.getColor("configuration_item_text")
+
+ function updateBorderColor()
+ {
+ border.color = selected ? UM.Theme.getColor("configuration_item_border_active") : UM.Theme.getColor("configuration_item_border")
+ }
+
+ Column
+ {
+ id: contentColumn
+ width: parent.width
+ padding: UM.Theme.getSize("default_margin").width
+ spacing: Math.round(UM.Theme.getSize("default_margin").height / 2)
+
+ Row
+ {
+ id: extruderRow
+
+ width: parent.width - 2 * parent.padding
+ height: childrenRect.height
+
+ spacing: UM.Theme.getSize("default_margin").width
+
+ Repeater
+ {
+ id: repeater
+ height: childrenRect.height
+ model: configuration.extruderConfigurations
+ delegate: PrintCoreConfiguration
+ {
+ width: Math.round(parent.width / 2)
+ printCoreConfiguration: modelData
+ mainColor: textColor
+ }
+ }
+ }
+
+ //Buildplate row separator
+ Rectangle
+ {
+ id: separator
+
+ visible: buildplateInformation.visible
+ width: parent.width - 2 * parent.padding
+ height: visible ? Math.round(UM.Theme.getSize("sidebar_lining_thin").height / 2) : 0
+ color: textColor
+ }
+
+ Item
+ {
+ id: buildplateInformation
+ width: parent.width - 2 * parent.padding
+ height: childrenRect.height
+ visible: configuration.buildplateConfiguration != ""
+
+ UM.RecolorImage {
+ id: buildplateIcon
+ anchors.left: parent.left
+ width: UM.Theme.getSize("topbar_button_icon").width
+ height: UM.Theme.getSize("topbar_button_icon").height
+ sourceSize.width: width
+ sourceSize.height: height
+ source: UM.Theme.getIcon("buildplate")
+ color: textColor
+ }
+
+ Label
+ {
+ id: buildplateLabel
+ anchors.left: buildplateIcon.right
+ anchors.verticalCenter: buildplateIcon.verticalCenter
+ anchors.leftMargin: Math.round(UM.Theme.getSize("default_margin").height / 2)
+ text: configuration.buildplateConfiguration
+ color: textColor
+ }
+ }
+ }
+
+ MouseArea
+ {
+ id: mouse
+ anchors.fill: parent
+ onClicked: activateConfiguration()
+ hoverEnabled: true
+ onEntered: parent.border.color = UM.Theme.getColor("configuration_item_border_hover")
+ onExited: updateBorderColor()
+ }
+
+ Connections
+ {
+ target: Cura.MachineManager
+ onCurrentConfigurationChanged: {
+ configurationItem.selected = Cura.MachineManager.matchesConfiguration(configuration)
+ updateBorderColor()
+ }
+ }
+
+ Component.onCompleted:
+ {
+ configurationItem.selected = Cura.MachineManager.matchesConfiguration(configuration)
+ updateBorderColor()
+ }
+}
\ No newline at end of file
diff --git a/resources/qml/Menus/ConfigurationMenu/ConfigurationListView.qml b/resources/qml/Menus/ConfigurationMenu/ConfigurationListView.qml
new file mode 100644
index 0000000000..331d78ead9
--- /dev/null
+++ b/resources/qml/Menus/ConfigurationMenu/ConfigurationListView.qml
@@ -0,0 +1,102 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.7
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+Column
+{
+ id: base
+ property var outputDevice: null
+ property var computedHeight: container.height + configurationListHeading.height + 3 * padding
+ height: childrenRect.height + 2 * padding
+ padding: UM.Theme.getSize("default_margin").width
+ spacing: Math.round(UM.Theme.getSize("default_margin").height / 2)
+
+ function forceModelUpdate()
+ {
+ // FIXME For now the model should be removed and then created again, otherwise changes in the printer don't automatically update the UI
+ configurationList.model = []
+ configurationList.model = outputDevice.uniqueConfigurations
+ }
+
+ Label
+ {
+ id: configurationListHeading
+ text: catalog.i18nc("@label:header configurations", "Available configurations")
+ font: UM.Theme.getFont("large")
+ width: parent.width - 2 * parent.padding
+ color: UM.Theme.getColor("configuration_item_text")
+ }
+
+ Component
+ {
+ id: sectionHeading
+ Rectangle
+ {
+ height: childrenRect.height + UM.Theme.getSize("default_margin").height
+ Label
+ {
+ text: section
+ font: UM.Theme.getFont("default_bold")
+ color: UM.Theme.getColor("configuration_item_text")
+ }
+ }
+ }
+
+ ScrollView
+ {
+ id: container
+ width: parent.width - parent.padding
+ height: Math.min(configurationList.contentHeight, 350 * screenScaleFactor)
+
+ style: UM.Theme.styles.scrollview
+ __wheelAreaScrollSpeed: 75 // Scroll three lines in one scroll event
+
+ ListView
+ {
+ id: configurationList
+ spacing: Math.round(UM.Theme.getSize("default_margin").height / 2)
+ width: container.width
+ contentHeight: childrenRect.height
+
+ section.property: "modelData.printerType"
+ section.criteria: ViewSection.FullString
+ section.delegate: sectionHeading
+
+ model: (outputDevice != null) ? outputDevice.uniqueConfigurations : []
+ delegate: ConfigurationItem
+ {
+ width: parent.width - UM.Theme.getSize("default_margin").width
+ configuration: modelData
+ onActivateConfiguration:
+ {
+ switchPopupState()
+ Cura.MachineManager.applyRemoteConfiguration(configuration)
+ }
+ }
+ }
+ }
+
+ Connections
+ {
+ target: outputDevice
+ onUniqueConfigurationsChanged:
+ {
+ forceModelUpdate()
+ }
+ }
+
+ Connections
+ {
+ target: Cura.MachineManager
+ onOutputDevicesChanged:
+ {
+ forceModelUpdate()
+ }
+ }
+}
\ No newline at end of file
diff --git a/resources/qml/Menus/ConfigurationMenu/ConfigurationSelection.qml b/resources/qml/Menus/ConfigurationMenu/ConfigurationSelection.qml
new file mode 100644
index 0000000000..d7ee2c68ee
--- /dev/null
+++ b/resources/qml/Menus/ConfigurationMenu/ConfigurationSelection.qml
@@ -0,0 +1,65 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.7
+import QtQuick.Controls 2.0
+import QtQuick.Controls.Styles 1.4
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+Item
+{
+ id: configurationSelector
+ property var connectedDevice: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null
+ property var panelWidth: control.width
+
+ function switchPopupState()
+ {
+ popup.visible ? popup.close() : popup.open()
+ }
+
+ SyncButton
+ {
+ id: syncButton
+ onClicked: switchPopupState()
+ outputDevice: connectedDevice
+ }
+
+ Popup
+ {
+ // TODO Change once updating to Qt5.10 - The 'opened' property is in 5.10 but the behavior is now implemented with the visible property
+ id: popup
+ clip: true
+ closePolicy: Popup.CloseOnPressOutsideParent
+ y: configurationSelector.height - UM.Theme.getSize("default_lining").height
+ x: configurationSelector.width - width
+ width: panelWidth
+ visible: false
+ padding: UM.Theme.getSize("default_lining").width
+ transformOrigin: Popup.Top
+ contentItem: ConfigurationListView
+ {
+ id: configList
+ width: panelWidth - 2 * popup.padding
+ outputDevice: connectedDevice
+ }
+ background: Rectangle
+ {
+ color: UM.Theme.getColor("setting_control")
+ border.color: UM.Theme.getColor("setting_control_border")
+ }
+ exit: Transition
+ {
+ // This applies a default NumberAnimation to any changes a state change makes to x or y properties
+ NumberAnimation { property: "visible"; duration: 75; }
+ }
+ enter: Transition
+ {
+ // This applies a default NumberAnimation to any changes a state change makes to x or y properties
+ NumberAnimation { property: "visible"; duration: 75; }
+ }
+ onClosed: visible = false
+ onOpened: visible = true
+ }
+}
\ No newline at end of file
diff --git a/resources/qml/Menus/ConfigurationMenu/PrintCoreConfiguration.qml b/resources/qml/Menus/ConfigurationMenu/PrintCoreConfiguration.qml
new file mode 100644
index 0000000000..ca1b666e69
--- /dev/null
+++ b/resources/qml/Menus/ConfigurationMenu/PrintCoreConfiguration.qml
@@ -0,0 +1,87 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.7
+import QtQuick.Controls 2.0
+
+import UM 1.2 as UM
+
+
+Column
+{
+ id: extruderInfo
+ property var printCoreConfiguration
+ property var mainColor: "black"
+ spacing: Math.round(UM.Theme.getSize("default_margin").height / 2)
+
+ height: childrenRect.height
+
+ Item
+ {
+ id: extruder
+ width: parent.width
+ height: childrenRect.height
+
+ Label
+ {
+ id: extruderLabel
+ text: catalog.i18nc("@label:extruder label", "Extruder")
+ elide: Text.ElideRight
+ anchors.left: parent.left
+ font: UM.Theme.getFont("default")
+ color: mainColor
+ }
+
+ // Rounded item to show the extruder number
+ Item
+ {
+ id: extruderIconItem
+ anchors.verticalCenter: extruderLabel.verticalCenter
+ anchors.left: extruderLabel.right
+ anchors.leftMargin: Math.round(UM.Theme.getSize("default_margin").width / 2)
+
+ width: UM.Theme.getSize("section_icon").width
+ height: UM.Theme.getSize("section_icon").height
+
+ UM.RecolorImage {
+ id: mainCircle
+ anchors.fill: parent
+
+ anchors.centerIn: parent
+ sourceSize.width: parent.width
+ sourceSize.height: parent.height
+ source: UM.Theme.getIcon("extruder_button")
+ color: mainColor
+ }
+
+ Label
+ {
+ id: extruderNumberText
+ anchors.centerIn: parent
+ text: printCoreConfiguration.position + 1
+ font: UM.Theme.getFont("default")
+ color: mainColor
+ }
+ }
+ }
+
+ Label
+ {
+ id: materialLabel
+ text: printCoreConfiguration.material.name
+ elide: Text.ElideRight
+ width: parent.width
+ font: UM.Theme.getFont("default_bold")
+ color: mainColor
+ }
+
+ Label
+ {
+ id: printCoreTypeLabel
+ text: printCoreConfiguration.hotendID
+ elide: Text.ElideRight
+ width: parent.width
+ font: UM.Theme.getFont("default")
+ color: mainColor
+ }
+}
diff --git a/resources/qml/Menus/ConfigurationMenu/SyncButton.qml b/resources/qml/Menus/ConfigurationMenu/SyncButton.qml
new file mode 100644
index 0000000000..6654708482
--- /dev/null
+++ b/resources/qml/Menus/ConfigurationMenu/SyncButton.qml
@@ -0,0 +1,107 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.7
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+Button
+{
+ id: base
+ property var outputDevice: null
+ property var matched: updateOnSync()
+ text: matched == true ? "Yes" : "No"
+ width: parent.width
+ height: parent.height
+
+ function updateOnSync()
+ {
+ if (outputDevice != undefined)
+ {
+ for (var index in outputDevice.uniqueConfigurations)
+ {
+ var configuration = outputDevice.uniqueConfigurations[index]
+ if (Cura.MachineManager.matchesConfiguration(configuration))
+ {
+ base.matched = true;
+ return;
+ }
+ }
+ }
+ base.matched = false;
+ }
+
+ style: ButtonStyle
+ {
+ background: Rectangle
+ {
+ color:
+ {
+ if(control.pressed)
+ {
+ return UM.Theme.getColor("sidebar_header_active");
+ }
+ else if(control.hovered)
+ {
+ return UM.Theme.getColor("sidebar_header_hover");
+ }
+ else
+ {
+ return UM.Theme.getColor("sidebar_header_bar");
+ }
+ }
+ Behavior on color { ColorAnimation { duration: 50; } }
+
+ UM.RecolorImage
+ {
+ id: downArrow
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.right: parent.right
+ anchors.rightMargin: UM.Theme.getSize("default_margin").width
+ width: UM.Theme.getSize("standard_arrow").width
+ height: UM.Theme.getSize("standard_arrow").height
+ sourceSize.width: width
+ sourceSize.height: height
+ color: UM.Theme.getColor("text_emphasis")
+ source: UM.Theme.getIcon("arrow_bottom")
+ }
+ UM.RecolorImage
+ {
+ id: sidebarComboBoxLabel
+ anchors.left: parent.left
+ anchors.leftMargin: UM.Theme.getSize("default_margin").width
+ anchors.verticalCenter: parent.verticalCenter;
+
+ width: UM.Theme.getSize("printer_sync_icon").width
+ height: UM.Theme.getSize("printer_sync_icon").height
+
+ color: control.matched ? UM.Theme.getColor("printer_config_matched") : UM.Theme.getColor("printer_config_mismatch")
+ source: UM.Theme.getIcon("tab_status_connected")
+ sourceSize.width: width
+ sourceSize.height: height
+ }
+ }
+ label: Label {}
+ }
+
+ Connections
+ {
+ target: outputDevice
+ onUniqueConfigurationsChanged: updateOnSync()
+ }
+
+ Connections
+ {
+ target: Cura.MachineManager
+ onCurrentConfigurationChanged: updateOnSync()
+ }
+
+ Connections
+ {
+ target: Cura.MachineManager
+ onOutputDevicesChanged: updateOnSync()
+ }
+}
\ No newline at end of file
diff --git a/resources/qml/Menus/ContextMenu.qml b/resources/qml/Menus/ContextMenu.qml
index 83302f9463..e35aef5f20 100644
--- a/resources/qml/Menus/ContextMenu.qml
+++ b/resources/qml/Menus/ContextMenu.qml
@@ -31,7 +31,7 @@ Menu
MenuItem {
text: "%1: %2 - %3".arg(model.name).arg(model.material).arg(model.variant)
visible: base.shouldShowExtruders
- enabled: UM.Selection.hasSelection
+ enabled: UM.Selection.hasSelection && model.enabled
checkable: true
checked: Cura.ExtruderManager.selectedObjectExtruders.indexOf(model.id) != -1
onTriggered: CuraActions.setExtruderForSelection(model.id)
diff --git a/resources/qml/Menus/LocalPrinterMenu.qml b/resources/qml/Menus/LocalPrinterMenu.qml
new file mode 100644
index 0000000000..0bdd4f33b9
--- /dev/null
+++ b/resources/qml/Menus/LocalPrinterMenu.qml
@@ -0,0 +1,23 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.2
+import QtQuick.Controls 1.4
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+Instantiator {
+ model: UM.ContainerStacksModel {
+ filter: {"type": "machine", "um_network_key": null}
+ }
+ MenuItem {
+ text: model.name;
+ checkable: true;
+ checked: Cura.MachineManager.activeMachineId == model.id
+ exclusiveGroup: group;
+ onTriggered: Cura.MachineManager.setActiveMachine(model.id);
+ }
+ onObjectAdded: menu.insertItem(index, object)
+ onObjectRemoved: menu.removeItem(object)
+}
diff --git a/resources/qml/Menus/NetworkPrinterMenu.qml b/resources/qml/Menus/NetworkPrinterMenu.qml
new file mode 100644
index 0000000000..07a22202e4
--- /dev/null
+++ b/resources/qml/Menus/NetworkPrinterMenu.qml
@@ -0,0 +1,25 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.2
+import QtQuick.Controls 1.4
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+Instantiator {
+ model: UM.ContainerStacksModel {
+ filter: {"type": "machine", "um_network_key": "*", "hidden": "False"}
+ }
+ MenuItem {
+ // TODO: Use printer_group icon when it's a cluster. Not use it for now since it doesn't look as expected
+// iconSource: UM.Theme.getIcon("printer_single")
+ text: model.metadata["connect_group_name"]
+ checkable: true;
+ checked: Cura.MachineManager.activeMachineNetworkGroupName == model.metadata["connect_group_name"]
+ exclusiveGroup: group;
+ onTriggered: Cura.MachineManager.setActiveMachine(model.id);
+ }
+ onObjectAdded: menu.insertItem(index, object)
+ onObjectRemoved: menu.removeItem(object)
+}
diff --git a/resources/qml/Menus/NozzleMenu.qml b/resources/qml/Menus/NozzleMenu.qml
index 43f3b79dd4..886216dab0 100644
--- a/resources/qml/Menus/NozzleMenu.qml
+++ b/resources/qml/Menus/NozzleMenu.qml
@@ -32,7 +32,7 @@ Menu
}
exclusiveGroup: group
onTriggered: {
- Cura.MachineManager.setVariantGroup(menu.extruderIndex, model.container_node);
+ Cura.MachineManager.setVariant(menu.extruderIndex, model.container_node);
}
}
diff --git a/resources/qml/Menus/PrinterMenu.qml b/resources/qml/Menus/PrinterMenu.qml
index 073723a60d..741d927c13 100644
--- a/resources/qml/Menus/PrinterMenu.qml
+++ b/resources/qml/Menus/PrinterMenu.qml
@@ -1,37 +1,60 @@
-// Copyright (c) 2016 Ultimaker B.V.
+// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
-import QtQuick.Controls 1.1
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
import UM 1.2 as UM
import Cura 1.0 as Cura
Menu
{
- id: menu;
+ id: menu
+// TODO Enable custom style to the menu
+// style: MenuStyle
+// {
+// frame: Rectangle
+// {
+// color: "white"
+// }
+// }
- Instantiator
+ MenuItem
{
- model: UM.ContainerStacksModel
- {
- filter: {"type": "machine"}
- }
- MenuItem
- {
- text: model.name;
- checkable: true;
- checked: Cura.MachineManager.activeMachineId == model.id
- exclusiveGroup: group;
- onTriggered: Cura.MachineManager.setActiveMachine(model.id);
- }
- onObjectAdded: menu.insertItem(index, object)
- onObjectRemoved: menu.removeItem(object)
+ text: catalog.i18nc("@label:category menu label", "Network enabled printers")
+ enabled: false
+ visible: networkPrinterMenu.count > 0
+ }
+
+ NetworkPrinterMenu
+ {
+ id: networkPrinterMenu
+ }
+
+ MenuSeparator
+ {
+ visible: networkPrinterMenu.count > 0
+ }
+
+ MenuItem
+ {
+ text: catalog.i18nc("@label:category menu label", "Local printers")
+ enabled: false
+ visible: localPrinterMenu.count > 0
+ }
+
+ LocalPrinterMenu
+ {
+ id: localPrinterMenu
}
ExclusiveGroup { id: group; }
- MenuSeparator { }
+ MenuSeparator
+ {
+ visible: localPrinterMenu.count > 0
+ }
MenuItem { action: Cura.Actions.addMachine; }
MenuItem { action: Cura.Actions.configureMachines; }
diff --git a/resources/qml/Menus/PrinterStatusIcon.qml b/resources/qml/Menus/PrinterStatusIcon.qml
new file mode 100644
index 0000000000..6ff6b07af8
--- /dev/null
+++ b/resources/qml/Menus/PrinterStatusIcon.qml
@@ -0,0 +1,27 @@
+// Copyright (c) 2017 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.2
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+Item
+{
+ property var status: "disconnected"
+ width: childrenRect.width
+ height: childrenRect.height
+ UM.RecolorImage
+ {
+ id: statusIcon
+ width: UM.Theme.getSize("printer_status_icon").width
+ height: UM.Theme.getSize("printer_status_icon").height
+ sourceSize.width: width
+ sourceSize.height: width
+ color: UM.Theme.getColor("tab_status_" + parent.status)
+ source: UM.Theme.getIcon(parent.status)
+ }
+}
+
+
+
diff --git a/resources/qml/Menus/PrinterTypeMenu.qml b/resources/qml/Menus/PrinterTypeMenu.qml
new file mode 100644
index 0000000000..28bdca54d9
--- /dev/null
+++ b/resources/qml/Menus/PrinterTypeMenu.qml
@@ -0,0 +1,37 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.7
+import QtQuick.Controls 1.4
+
+import UM 1.3 as UM
+import Cura 1.0 as Cura
+
+Menu
+{
+ id: menu
+ title: "Printer type"
+ property var outputDevice: Cura.MachineManager.printerOutputDevices[0]
+
+ Instantiator
+ {
+ id: printerTypeInstantiator
+ model: outputDevice != null ? outputDevice.connectedPrintersTypeCount : []
+
+ MenuItem
+ {
+ text: modelData.machine_type
+ checkable: true
+ checked: Cura.MachineManager.activeMachineDefinitionName == modelData.machine_type
+ exclusiveGroup: group
+ onTriggered:
+ {
+ Cura.MachineManager.switchPrinterType(modelData.machine_type)
+ }
+ }
+ onObjectAdded: menu.insertItem(index, object)
+ onObjectRemoved: menu.removeItem(object)
+ }
+
+ ExclusiveGroup { id: group }
+}
diff --git a/resources/qml/Menus/ProfileMenu.qml b/resources/qml/Menus/ProfileMenu.qml
index 72cda13ca9..5b9a5a3b73 100644
--- a/resources/qml/Menus/ProfileMenu.qml
+++ b/resources/qml/Menus/ProfileMenu.qml
@@ -52,6 +52,7 @@ Menu
{
text: model.name
checkable: model.available
+ enabled: model.available
checked: Cura.MachineManager.activeQualityOrQualityChangesName == model.name
exclusiveGroup: group
onTriggered: Cura.MachineManager.setQualityChangesGroup(model.quality_changes_group)
diff --git a/resources/qml/Menus/SettingVisibilityPresetsMenu.qml b/resources/qml/Menus/SettingVisibilityPresetsMenu.qml
new file mode 100644
index 0000000000..2175cfa402
--- /dev/null
+++ b/resources/qml/Menus/SettingVisibilityPresetsMenu.qml
@@ -0,0 +1,60 @@
+// Copyright (c) 2018 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.7
+import QtQuick.Controls 1.4
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+Menu
+{
+ id: menu
+ title: catalog.i18nc("@action:inmenu", "Visible Settings")
+
+ property QtObject settingVisibilityPresetsModel: CuraApplication.getSettingVisibilityPresetsModel()
+
+ signal showAllSettings()
+
+ Instantiator
+ {
+ model: settingVisibilityPresetsModel
+
+ MenuItem
+ {
+ text: model.name
+ checkable: true
+ checked: model.id == settingVisibilityPresetsModel.activePreset
+ exclusiveGroup: group
+ onTriggered:
+ {
+ settingVisibilityPresetsModel.setActivePreset(model.id);
+ showSettingVisibilityProfile();
+ }
+ }
+
+ onObjectAdded: menu.insertItem(index, object)
+ onObjectRemoved: menu.removeItem(object)
+ }
+
+ MenuSeparator {}
+ MenuItem
+ {
+ text: catalog.i18nc("@action:inmenu", "Show All Settings")
+ checkable: false
+ exclusiveGroup: group
+ onTriggered:
+ {
+ showAllSettings();
+ }
+ }
+ MenuSeparator {}
+ MenuItem
+ {
+ text: catalog.i18nc("@action:inmenu", "Manage Setting Visibility...")
+ iconName: "configure"
+ onTriggered: Cura.Actions.configureSettingVisibility.trigger()
+ }
+
+ ExclusiveGroup { id: group }
+}
diff --git a/resources/qml/Preferences/MachinesPage.qml b/resources/qml/Preferences/MachinesPage.qml
index 62e5ef98b4..665586d29f 100644
--- a/resources/qml/Preferences/MachinesPage.qml
+++ b/resources/qml/Preferences/MachinesPage.qml
@@ -14,10 +14,7 @@ UM.ManagementPage
id: base;
title: catalog.i18nc("@title:tab", "Printers");
- model: UM.ContainerStacksModel
- {
- filter: {"type": "machine"}
- }
+ model: Cura.MachineManagementModel { }
activeId: Cura.MachineManager.activeMachineId
activeIndex: activeMachineIndex()
@@ -57,7 +54,7 @@ UM.ManagementPage
{
text: catalog.i18nc("@action:button", "Rename");
iconName: "edit-rename";
- enabled: base.currentItem != null
+ enabled: base.currentItem != null && base.currentItem.metadata.connect_group_name == null
onClicked: renameDialog.open();
}
]
diff --git a/resources/qml/Preferences/MaterialView.qml b/resources/qml/Preferences/MaterialView.qml
index c987880305..3b1c10fbbd 100644
--- a/resources/qml/Preferences/MaterialView.qml
+++ b/resources/qml/Preferences/MaterialView.qml
@@ -36,8 +36,8 @@ TabView
if (!base.containerId || !base.editingEnabled) {
return ""
}
- var linkedMaterials = Cura.ContainerManager.getLinkedMaterials(base.currentMaterialNode);
- if (linkedMaterials.length <= 1) {
+ var linkedMaterials = Cura.ContainerManager.getLinkedMaterials(base.currentMaterialNode, true);
+ if (linkedMaterials.length == 0) {
return ""
}
return linkedMaterials.join(", ");
@@ -47,6 +47,19 @@ TabView
return Math.round(diameter);
}
+ // This trick makes sure to make all fields lose focus so their onEditingFinished will be triggered
+ // and modified values will be saved. This can happen when a user changes a value and then closes the
+ // dialog directly.
+ //
+ // Please note that somehow this callback is ONLY triggered when visible is false.
+ onVisibleChanged:
+ {
+ if (!visible)
+ {
+ base.focus = false;
+ }
+ }
+
Tab
{
title: catalog.i18nc("@title", "Information")
@@ -86,6 +99,7 @@ TabView
property var new_diameter_value: null;
property var old_diameter_value: null;
property var old_approximate_diameter_value: null;
+ property bool keyPressed: false
onYes:
{
@@ -99,6 +113,16 @@ TabView
properties.diameter = old_diameter_value;
diameterSpinBox.value = properties.diameter;
}
+
+ onVisibilityChanged:
+ {
+ if (!visible && !keyPressed)
+ {
+ // If the user closes this dialog without clicking on any button, it's the same as clicking "No".
+ no();
+ }
+ keyPressed = false;
+ }
}
Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Display Name") }
@@ -209,7 +233,7 @@ TabView
var old_diameter = Cura.ContainerManager.getContainerProperty(base.containerId, "material_diameter", "value").toString();
var old_approximate_diameter = Cura.ContainerManager.getContainerMetaDataEntry(base.containerId, "approximate_diameter");
var new_approximate_diameter = getApproximateDiameter(value);
- if (Cura.MachineManager.filterMaterialsByMachine && new_approximate_diameter != Cura.ExtruderManager.getActiveExtruderStack().approximateMaterialDiameter)
+ if (new_approximate_diameter != Cura.ExtruderManager.getActiveExtruderStack().approximateMaterialDiameter)
{
confirmDiameterChangeDialog.old_diameter_value = old_diameter;
confirmDiameterChangeDialog.new_diameter_value = value;
diff --git a/resources/qml/Preferences/MaterialsPage.qml b/resources/qml/Preferences/MaterialsPage.qml
index 4a6d07df81..042bd09828 100644
--- a/resources/qml/Preferences/MaterialsPage.qml
+++ b/resources/qml/Preferences/MaterialsPage.qml
@@ -19,14 +19,17 @@ Item
UM.I18nCatalog { id: catalog; name: "cura"; }
- Cura.MaterialManagementModel {
+ Cura.MaterialManagementModel
+ {
id: materialsModel
}
- Label {
+ Label
+ {
id: titleLabel
- anchors {
+ anchors
+ {
top: parent.top
left: parent.left
right: parent.right
@@ -52,6 +55,24 @@ Item
return base.currentItem.root_material_id == root_material_id;
}
+ Component.onCompleted:
+ {
+ // Select the activated material when this page shows up
+ const extruder_position = Cura.ExtruderManager.activeExtruderIndex;
+ const active_root_material_id = Cura.MachineManager.currentRootMaterialId[extruder_position];
+ var itemIndex = -1;
+ for (var i = 0; i < materialsModel.rowCount(); ++i)
+ {
+ var item = materialsModel.getItem(i);
+ if (item.root_material_id == active_root_material_id)
+ {
+ itemIndex = i;
+ break;
+ }
+ }
+ materialListView.currentIndex = itemIndex;
+ }
+
Row // Button Row
{
id: buttonRow
@@ -152,22 +173,27 @@ Item
Connections
{
target: materialsModel
- onItemsChanged: {
+ onItemsChanged:
+ {
var currentItemId = base.currentItem == null ? "" : base.currentItem.root_material_id;
var position = Cura.ExtruderManager.activeExtruderIndex;
// try to pick the currently selected item; it may have been moved
- if (base.newRootMaterialIdToSwitchTo == "") {
+ if (base.newRootMaterialIdToSwitchTo == "")
+ {
base.newRootMaterialIdToSwitchTo = currentItemId;
}
- for (var idx = 0; idx < materialsModel.rowCount(); ++idx) {
+ for (var idx = 0; idx < materialsModel.rowCount(); ++idx)
+ {
var item = materialsModel.getItem(idx);
- if (item.root_material_id == base.newRootMaterialIdToSwitchTo) {
+ if (item.root_material_id == base.newRootMaterialIdToSwitchTo)
+ {
// Switch to the newly created profile if needed
materialListView.currentIndex = idx;
materialListView.activateDetailsWithIndex(materialListView.currentIndex);
- if (base.toActivateNewMaterial) {
+ if (base.toActivateNewMaterial)
+ {
Cura.MachineManager.setMaterial(position, item.container_node);
}
base.newRootMaterialIdToSwitchTo = "";
@@ -178,7 +204,8 @@ Item
materialListView.currentIndex = 0;
materialListView.activateDetailsWithIndex(materialListView.currentIndex);
- if (base.toActivateNewMaterial) {
+ if (base.toActivateNewMaterial)
+ {
Cura.MachineManager.setMaterial(position, materialsModel.getItem(0).container_node);
}
base.newRootMaterialIdToSwitchTo = "";
@@ -215,14 +242,17 @@ Item
messageDialog.title = catalog.i18nc("@title:window", "Import Material");
messageDialog.text = catalog.i18nc("@info:status Don't translate the XML tags or !", "Could not import material %1 : %2 ").arg(fileUrl).arg(result.message);
- if (result.status == "success") {
+ if (result.status == "success")
+ {
messageDialog.icon = StandardIcon.Information;
messageDialog.text = catalog.i18nc("@info:status Don't translate the XML tag !", "Successfully imported material %1 ").arg(fileUrl);
}
- else if (result.status == "duplicate") {
+ else if (result.status == "duplicate")
+ {
messageDialog.icon = StandardIcon.Warning;
}
- else {
+ else
+ {
messageDialog.icon = StandardIcon.Critical;
}
messageDialog.open();
@@ -242,12 +272,14 @@ Item
var result = Cura.ContainerManager.exportContainer(base.currentItem.root_material_id, selectedNameFilter, fileUrl);
messageDialog.title = catalog.i18nc("@title:window", "Export Material");
- if (result.status == "error") {
+ if (result.status == "error")
+ {
messageDialog.icon = StandardIcon.Critical;
messageDialog.text = catalog.i18nc("@info:status Don't translate the XML tags and !", "Failed to export material to %1 : %2 ").arg(fileUrl).arg(result.message);
messageDialog.open();
}
- else if (result.status == "success") {
+ else if (result.status == "success")
+ {
messageDialog.icon = StandardIcon.Information;
messageDialog.text = catalog.i18nc("@info:status Don't translate the XML tag !", "Successfully exported material to %1 ").arg(result.path);
messageDialog.open();
@@ -265,7 +297,8 @@ Item
Item {
id: contentsItem
- anchors {
+ anchors
+ {
top: titleLabel.bottom
left: parent.left
right: parent.right
@@ -279,7 +312,8 @@ Item
Item
{
- anchors {
+ anchors
+ {
top: buttonRow.bottom
topMargin: UM.Theme.getSize("default_margin").height
left: parent.left
@@ -292,12 +326,14 @@ Item
Label
{
id: captionLabel
- anchors {
+ anchors
+ {
top: parent.top
left: parent.left
}
visible: text != ""
- text: {
+ text:
+ {
var caption = catalog.i18nc("@action:label", "Printer") + ": " + Cura.MachineManager.activeMachineName;
if (Cura.MachineManager.hasVariants)
{
@@ -312,20 +348,23 @@ Item
ScrollView
{
id: materialScrollView
- anchors {
+ anchors
+ {
top: captionLabel.visible ? captionLabel.bottom : parent.top
topMargin: captionLabel.visible ? UM.Theme.getSize("default_margin").height : 0
bottom: parent.bottom
left: parent.left
}
- Rectangle {
+ Rectangle
+ {
parent: viewport
anchors.fill: parent
color: palette.light
}
width: true ? (parent.width * 0.4) | 0 : parent.width
+ frameVisible: true
ListView
{
@@ -400,13 +439,15 @@ Item
MouseArea
{
anchors.fill: parent
- onClicked: {
+ onClicked:
+ {
parent.ListView.view.currentIndex = model.index;
}
}
}
- function activateDetailsWithIndex(index) {
+ function activateDetailsWithIndex(index)
+ {
var model = materialsModel.getItem(index);
base.currentItem = model;
materialDetailsView.containerId = model.container_id;
@@ -428,7 +469,8 @@ Item
{
id: detailsPanel
- anchors {
+ anchors
+ {
left: materialScrollView.right
leftMargin: UM.Theme.getSize("default_margin").width
top: parent.top
diff --git a/resources/qml/Preferences/ProfileTab.qml b/resources/qml/Preferences/ProfileTab.qml
index 40f725e092..0ae0899051 100644
--- a/resources/qml/Preferences/ProfileTab.qml
+++ b/resources/qml/Preferences/ProfileTab.qml
@@ -11,9 +11,18 @@ Tab
{
id: base
- property string extruderPosition: ""
+ property int extruderPosition: -1 //Denotes the global stack.
property var qualityItem: null
+ property bool isQualityItemCurrentlyActivated:
+ {
+ if (qualityItem == null)
+ {
+ return false;
+ }
+ return qualityItem.name == Cura.MachineManager.activeQualityOrQualityChangesName;
+ }
+
TableView
{
anchors.fill: parent
@@ -36,8 +45,8 @@ Tab
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.right: parent.right
text: (styleData.value.substr(0,1) == "=") ? catalog.i18nc("@info:status", "Calculated") : styleData.value
- font.strikeout: styleData.column == 1 && setting.user_value != "" && qualityItem.name == Cura.MachineManager.activeQualityOrQualityChangesName
- font.italic: setting.profile_value_source == "quality_changes" || (setting.user_value != "" && qualityItem.name == Cura.MachineManager.activeQualityOrQualityChangesName)
+ font.strikeout: styleData.column == 1 && setting.user_value != "" && base.isQualityItemCurrentlyActivated
+ font.italic: setting.profile_value_source == "quality_changes" || (setting.user_value != "" && base.isQualityItemCurrentlyActivated)
opacity: font.strikeout ? 0.5 : 1
color: styleData.textColor
elide: Text.ElideRight
@@ -63,7 +72,7 @@ Tab
{
role: "user_value"
title: catalog.i18nc("@title:column", "Current");
- visible: qualityItem.name == Cura.MachineManager.activeQualityOrQualityChangesName
+ visible: base.isQualityItemCurrentlyActivated
width: (parent.width * 0.18) | 0
delegate: itemDelegate
}
@@ -86,7 +95,7 @@ Tab
{
id: qualitySettings
selectedPosition: base.extruderPosition
- selectedQualityItem: base.qualityItem
+ selectedQualityItem: base.qualityItem == null ? {} : base.qualityItem
}
SystemPalette { id: palette }
diff --git a/resources/qml/Preferences/ProfilesPage.qml b/resources/qml/Preferences/ProfilesPage.qml
index 3e10aca000..1726087e97 100644
--- a/resources/qml/Preferences/ProfilesPage.qml
+++ b/resources/qml/Preferences/ProfilesPage.qml
@@ -36,13 +36,15 @@ Item
text: catalog.i18nc("@title:tab", "Profiles")
}
- property var hasCurrentItem: qualityListView.currentItem != null
+ property var hasCurrentItem: base.currentItem != null
property var currentItem: {
var current_index = qualityListView.currentIndex;
- return qualitiesModel.getItem(current_index);
+ return (current_index == -1) ? null : qualitiesModel.getItem(current_index);
}
+ property var currentItemName: hasCurrentItem ? base.currentItem.name : ""
+
property var isCurrentItemActivated: {
if (!base.currentItem) {
return false;
@@ -235,7 +237,7 @@ Item
icon: StandardIcon.Question;
title: catalog.i18nc("@title:window", "Confirm Remove")
- text: catalog.i18nc("@label (%1 is object name)", "Are you sure you wish to remove %1? This cannot be undone!").arg(base.currentItem.name)
+ text: catalog.i18nc("@label (%1 is object name)", "Are you sure you wish to remove %1? This cannot be undone!").arg(base.currentItemName)
standardButtons: StandardButton.Yes | StandardButton.No
modality: Qt.ApplicationModal
@@ -367,6 +369,7 @@ Item
}
width: true ? (parent.width * 0.4) | 0 : parent.width
+ frameVisible: true
ListView
{
@@ -437,6 +440,7 @@ Item
Item
{
anchors.fill: parent
+ visible: base.currentItem != null
Item // Profile title Label
{
@@ -446,7 +450,7 @@ Item
height: childrenRect.height
Label {
- text: base.currentItem.name
+ text: base.currentItemName
font: UM.Theme.getFont("large")
}
}
diff --git a/resources/qml/Preferences/ReadOnlySpinBox.qml b/resources/qml/Preferences/ReadOnlySpinBox.qml
index 5d0666d306..1bbef82b1e 100644
--- a/resources/qml/Preferences/ReadOnlySpinBox.qml
+++ b/resources/qml/Preferences/ReadOnlySpinBox.qml
@@ -34,6 +34,8 @@ Item
anchors.fill: parent
onEditingFinished: base.editingFinished()
+ Keys.onEnterPressed: base.editingFinished()
+ Keys.onReturnPressed: base.editingFinished()
}
Label
diff --git a/resources/qml/Preferences/ReadOnlyTextField.qml b/resources/qml/Preferences/ReadOnlyTextField.qml
index 9407475a9b..38d07d7d6a 100644
--- a/resources/qml/Preferences/ReadOnlyTextField.qml
+++ b/resources/qml/Preferences/ReadOnlyTextField.qml
@@ -29,6 +29,8 @@ Item
anchors.fill: parent
onEditingFinished: base.editingFinished()
+ Keys.onEnterPressed: base.editingFinished()
+ Keys.onReturnPressed: base.editingFinished()
}
Label
diff --git a/resources/qml/Preferences/SettingVisibilityPage.qml b/resources/qml/Preferences/SettingVisibilityPage.qml
index 0e3069d194..0f39a3c047 100644
--- a/resources/qml/Preferences/SettingVisibilityPage.qml
+++ b/resources/qml/Preferences/SettingVisibilityPage.qml
@@ -13,6 +13,8 @@ UM.PreferencesPage
{
title: catalog.i18nc("@title:tab", "Setting Visibility");
+ property QtObject settingVisibilityPresetsModel: CuraApplication.getSettingVisibilityPresetsModel()
+
property int scrollToIndex: 0
signal scrollToSection( string key )
@@ -26,9 +28,8 @@ UM.PreferencesPage
UM.Preferences.resetPreference("general/visible_settings")
// After calling this function update Setting visibility preset combobox.
- // Reset should set "Basic" setting preset
- visibilityPreset.setBasicPreset()
-
+ // Reset should set default setting preset ("Basic")
+ visibilityPreset.currentIndex = 1
}
resetEnabled: true;
@@ -71,20 +72,11 @@ UM.PreferencesPage
{
if(parent.checkedState == Qt.Unchecked || parent.checkedState == Qt.PartiallyChecked)
{
- definitionsModel.setAllVisible(true)
+ definitionsModel.setAllExpandedVisible(true)
}
else
{
- definitionsModel.setAllVisible(false)
- }
-
- // After change set "Custom" option
-
- // If already "Custom" then don't do nothing
- if (visibilityPreset.currentIndex != visibilityPreset.model.count - 1)
- {
- visibilityPreset.currentIndex = visibilityPreset.model.count - 1
- UM.Preferences.setValue("general/preset_setting_visibility_choice", visibilityPreset.model.get(visibilityPreset.currentIndex).text)
+ definitionsModel.setAllExpandedVisible(false)
}
}
}
@@ -110,83 +102,33 @@ UM.PreferencesPage
ComboBox
{
- property int customOptionValue: 100
-
- function setBasicPreset()
- {
- var index = 0
- for(var i = 0; i < presetNamesList.count; ++i)
- {
- if(model.get(i).text == "Basic")
- {
- index = i;
- break;
- }
- }
-
- visibilityPreset.currentIndex = index
- }
-
id: visibilityPreset
- width: 150
+ width: 150 * screenScaleFactor
anchors
{
top: parent.top
right: parent.right
}
- model: ListModel
- {
- id: presetNamesList
- Component.onCompleted:
- {
- // returned value is Dictionary (Ex: {1:"Basic"}, The number 1 is the weight and sort by weight)
- var itemsDict = UM.Preferences.getValue("general/visible_settings_preset")
- var sorted = [];
- for(var key in itemsDict) {
- sorted[sorted.length] = key;
- }
-
- sorted.sort();
- for(var i = 0; i < sorted.length; i++) {
- presetNamesList.append({text: itemsDict[sorted[i]], value: i});
- }
-
- // By agreement lets "Custom" option will have value 100
- presetNamesList.append({text: "Custom", value: visibilityPreset.customOptionValue});
- }
- }
+ model: settingVisibilityPresetsModel
+ textRole: "name"
currentIndex:
{
// Load previously selected preset.
- var text = UM.Preferences.getValue("general/preset_setting_visibility_choice");
-
-
-
- var index = 0;
- for(var i = 0; i < presetNamesList.count; ++i)
+ var index = settingVisibilityPresetsModel.find("id", settingVisibilityPresetsModel.activePreset)
+ if (index == -1)
{
- if(model.get(i).text == text)
- {
- index = i;
- break;
- }
+ return 0
}
- return index;
+
+ return index
}
onActivated:
{
- // TODO What to do if user is selected "Custom from Combobox" ?
- if (model.get(index).text == "Custom"){
- UM.Preferences.setValue("general/preset_setting_visibility_choice", model.get(index).text)
- return
- }
-
- var newVisibleSettings = CuraApplication.getVisibilitySettingPreset(model.get(index).text)
- UM.Preferences.setValue("general/visible_settings", newVisibleSettings)
- UM.Preferences.setValue("general/preset_setting_visibility_choice", model.get(index).text)
+ var preset_id = settingVisibilityPresetsModel.getItem(index).id;
+ settingVisibilityPresetsModel.setActivePreset(preset_id);
}
}
@@ -216,7 +158,7 @@ UM.PreferencesPage
exclude: ["machine_settings", "command_line_settings"]
showAncestors: true
expanded: ["*"]
- visibilityHandler: UM.SettingPreferenceVisibilityHandler { }
+ visibilityHandler: UM.SettingPreferenceVisibilityHandler {}
}
delegate: Loader
@@ -259,19 +201,7 @@ UM.PreferencesPage
{
id: settingVisibilityItem;
- UM.SettingVisibilityItem {
-
- // after changing any visibility of settings, set the preset to the "Custom" option
- visibilityChangeCallback : function()
- {
- // If already "Custom" then don't do nothing
- if (visibilityPreset.currentIndex != visibilityPreset.model.count - 1)
- {
- visibilityPreset.currentIndex = visibilityPreset.model.count - 1
- UM.Preferences.setValue("general/preset_setting_visibility_choice", visibilityPreset.model.get(visibilityPreset.currentIndex).text)
- }
- }
- }
+ UM.SettingVisibilityItem { }
}
}
}
\ No newline at end of file
diff --git a/resources/qml/PrintMonitor.qml b/resources/qml/PrintMonitor.qml
index 0841144043..ae74170004 100644
--- a/resources/qml/PrintMonitor.qml
+++ b/resources/qml/PrintMonitor.qml
@@ -122,7 +122,7 @@ Column
{
label: catalog.i18nc("@label", "Printing Time")
value: activePrintJob != null ? getPrettyTime(activePrintJob.timeTotal) : ""
- width:base.width
+ width: base.width
visible: activePrinter != null
}
diff --git a/resources/qml/PrinterOutput/ExtruderBox.qml b/resources/qml/PrinterOutput/ExtruderBox.qml
index a7141262a9..56c86f1034 100644
--- a/resources/qml/PrinterOutput/ExtruderBox.qml
+++ b/resources/qml/PrinterOutput/ExtruderBox.qml
@@ -12,9 +12,20 @@ Item
property alias color: background.color
property var extruderModel
property var position: index
- //width: index == machineExtruderCount.properties.value - 1 && index % 2 == 0 ? extrudersGrid.width : Math.floor(extrudersGrid.width / 2 - UM.Theme.getSize("sidebar_lining_thin").width / 2)
implicitWidth: parent.width
implicitHeight: UM.Theme.getSize("sidebar_extruder_box").height
+
+ UM.SettingPropertyProvider
+ {
+ id: extruderTemperature
+ containerStackId: Cura.ExtruderManager.extruderIds[position]
+ key: "material_print_temperature"
+ watchedProperties: ["value", "minimum_value", "maximum_value", "resolve"]
+ storeIndex: 0
+
+ property var resolve: Cura.MachineManager.activeStackId != Cura.MachineManager.activeMachineId ? properties.resolve : "None"
+ }
+
Rectangle
{
id: background
@@ -34,12 +45,11 @@ Item
{
id: extruderTargetTemperature
text: Math.round(extruderModel.targetHotendTemperature) + "°C"
- //text: (connectedPrinter != null && connectedPrinter.hotendIds[index] != null && connectedPrinter.targetHotendTemperatures[index] != null) ? Math.round(connectedPrinter.targetHotendTemperatures[index]) + "°C" : ""
font: UM.Theme.getFont("small")
color: UM.Theme.getColor("text_inactive")
anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
- anchors.bottom: extruderTemperature.bottom
+ anchors.bottom: extruderCurrentTemperature.bottom
MouseArea //For tooltip.
{
@@ -52,7 +62,7 @@ Item
{
base.showTooltip(
base,
- {x: 0, y: extruderTargetTemperature.mapToItem(base, 0, -parent.height / 4).y},
+ {x: 0, y: extruderTargetTemperature.mapToItem(base, 0, Math.floor(-parent.height / 4)).y},
catalog.i18nc("@tooltip", "The target temperature of the hotend. The hotend will heat up or cool down towards this temperature. If this is 0, the hotend heating is turned off.")
);
}
@@ -65,9 +75,8 @@ Item
}
Label //Temperature indication.
{
- id: extruderTemperature
+ id: extruderCurrentTemperature
text: Math.round(extruderModel.hotendTemperature) + "°C"
- //text: (connectedPrinter != null && connectedPrinter.hotendIds[index] != null && connectedPrinter.hotendTemperatures[index] != null) ? Math.round(connectedPrinter.hotendTemperatures[index]) + "°C" : ""
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("large")
anchors.right: extruderTargetTemperature.left
@@ -76,7 +85,7 @@ Item
MouseArea //For tooltip.
{
- id: extruderTemperatureTooltipArea
+ id: extruderCurrentTemperatureTooltipArea
hoverEnabled: true
anchors.fill: parent
onHoveredChanged:
@@ -85,8 +94,8 @@ Item
{
base.showTooltip(
base,
- {x: 0, y: parent.mapToItem(base, 0, -parent.height / 4).y},
- catalog.i18nc("@tooltip", "The current temperature of this extruder.")
+ {x: 0, y: parent.mapToItem(base, 0, Math.floor(-parent.height / 4)).y},
+ catalog.i18nc("@tooltip", "The current temperature of this hotend.")
);
}
else
@@ -97,6 +106,272 @@ Item
}
}
+ Rectangle //Input field for pre-heat temperature.
+ {
+ id: preheatTemperatureControl
+ color: !enabled ? UM.Theme.getColor("setting_control_disabled") : showError ? UM.Theme.getColor("setting_validation_error_background") : UM.Theme.getColor("setting_validation_ok")
+ property var showError:
+ {
+ if(extruderTemperature.properties.maximum_value != "None" && extruderTemperature.properties.maximum_value < Math.floor(preheatTemperatureInput.text))
+ {
+ return true;
+ } else
+ {
+ return false;
+ }
+ }
+ enabled:
+ {
+ if (extruderModel == null)
+ {
+ return false; //Can't preheat if not connected.
+ }
+ if (!connectedPrinter.acceptsCommands)
+ {
+ return false; //Not allowed to do anything.
+ }
+ if (connectedPrinter.activePrinter && connectedPrinter.activePrinter.activePrintJob)
+ {
+ if((["printing", "pre_print", "resuming", "pausing", "paused", "error", "offline"]).indexOf(connectedPrinter.activePrinter.activePrintJob.state) != -1)
+ {
+ return false; //Printer is in a state where it can't react to pre-heating.
+ }
+ }
+ return true;
+ }
+ border.width: UM.Theme.getSize("default_lining").width
+ border.color: !enabled ? UM.Theme.getColor("setting_control_disabled_border") : preheatTemperatureInputMouseArea.containsMouse ? UM.Theme.getColor("setting_control_border_highlight") : UM.Theme.getColor("setting_control_border")
+ anchors.right: preheatButton.left
+ anchors.rightMargin: UM.Theme.getSize("default_margin").width
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: UM.Theme.getSize("default_margin").height
+ width: UM.Theme.getSize("monitor_preheat_temperature_control").width
+ height: UM.Theme.getSize("monitor_preheat_temperature_control").height
+ visible: extruderModel != null ? enabled && extruderModel.canPreHeatHotends && !extruderModel.isPreheating : true
+ Rectangle //Highlight of input field.
+ {
+ anchors.fill: parent
+ anchors.margins: UM.Theme.getSize("default_lining").width
+ color: UM.Theme.getColor("setting_control_highlight")
+ opacity: preheatTemperatureControl.hovered ? 1.0 : 0
+ }
+ MouseArea //Change cursor on hovering.
+ {
+ id: preheatTemperatureInputMouseArea
+ hoverEnabled: true
+ anchors.fill: parent
+ cursorShape: Qt.IBeamCursor
+
+ onHoveredChanged:
+ {
+ if (containsMouse)
+ {
+ base.showTooltip(
+ base,
+ {x: 0, y: preheatTemperatureInputMouseArea.mapToItem(base, 0, 0).y},
+ catalog.i18nc("@tooltip of temperature input", "The temperature to pre-heat the hotend to.")
+ );
+ }
+ else
+ {
+ base.hideTooltip();
+ }
+ }
+ }
+ Label
+ {
+ id: unit
+ anchors.right: parent.right
+ anchors.rightMargin: UM.Theme.getSize("setting_unit_margin").width
+ anchors.verticalCenter: parent.verticalCenter
+
+ text: "°C";
+ color: UM.Theme.getColor("setting_unit")
+ font: UM.Theme.getFont("default")
+ }
+ TextInput
+ {
+ id: preheatTemperatureInput
+ font: UM.Theme.getFont("default")
+ color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text")
+ selectByMouse: true
+ maximumLength: 5
+ enabled: parent.enabled
+ validator: RegExpValidator { regExp: /^-?[0-9]{0,9}[.,]?[0-9]{0,10}$/ } //Floating point regex.
+ anchors.left: parent.left
+ anchors.leftMargin: UM.Theme.getSize("setting_unit_margin").width
+ anchors.right: unit.left
+ anchors.verticalCenter: parent.verticalCenter
+ renderType: Text.NativeRendering
+
+ Component.onCompleted:
+ {
+ if (!extruderTemperature.properties.value)
+ {
+ text = "";
+ }
+ else
+ {
+ text = extruderTemperature.properties.value;
+ }
+ }
+ }
+ }
+
+ Button //The pre-heat button.
+ {
+ id: preheatButton
+ height: UM.Theme.getSize("setting_control").height
+ visible: extruderModel != null ? extruderModel.canPreHeatHotends: true
+ enabled:
+ {
+ if (!preheatTemperatureControl.enabled)
+ {
+ return false; //Not connected, not authenticated or printer is busy.
+ }
+ if (extruderModel.isPreheating)
+ {
+ return true;
+ }
+ if (extruderTemperature.properties.minimum_value != "None" && Math.floor(preheatTemperatureInput.text) < Math.floor(extruderTemperature.properties.minimum_value))
+ {
+ return false; //Target temperature too low.
+ }
+ if (extruderTemperature.properties.maximum_value != "None" && Math.floor(preheatTemperatureInput.text) > Math.floor(extruderTemperature.properties.maximum_value))
+ {
+ return false; //Target temperature too high.
+ }
+ if (Math.floor(preheatTemperatureInput.text) == 0)
+ {
+ return false; //Setting the temperature to 0 is not allowed (since that cancels the pre-heating).
+ }
+ return true; //Preconditions are met.
+ }
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ anchors.margins: UM.Theme.getSize("default_margin").width
+ style: ButtonStyle {
+ background: Rectangle
+ {
+ border.width: UM.Theme.getSize("default_lining").width
+ implicitWidth: actualLabel.contentWidth + (UM.Theme.getSize("default_margin").width * 2)
+ border.color:
+ {
+ if(!control.enabled)
+ {
+ return UM.Theme.getColor("action_button_disabled_border");
+ }
+ else if(control.pressed)
+ {
+ return UM.Theme.getColor("action_button_active_border");
+ }
+ else if(control.hovered)
+ {
+ return UM.Theme.getColor("action_button_hovered_border");
+ }
+ else
+ {
+ return UM.Theme.getColor("action_button_border");
+ }
+ }
+ color:
+ {
+ if(!control.enabled)
+ {
+ return UM.Theme.getColor("action_button_disabled");
+ }
+ else if(control.pressed)
+ {
+ return UM.Theme.getColor("action_button_active");
+ }
+ else if(control.hovered)
+ {
+ return UM.Theme.getColor("action_button_hovered");
+ }
+ else
+ {
+ return UM.Theme.getColor("action_button");
+ }
+ }
+ Behavior on color
+ {
+ ColorAnimation
+ {
+ duration: 50
+ }
+ }
+
+ Label
+ {
+ id: actualLabel
+ anchors.centerIn: parent
+ color:
+ {
+ if(!control.enabled)
+ {
+ return UM.Theme.getColor("action_button_disabled_text");
+ }
+ else if(control.pressed)
+ {
+ return UM.Theme.getColor("action_button_active_text");
+ }
+ else if(control.hovered)
+ {
+ return UM.Theme.getColor("action_button_hovered_text");
+ }
+ else
+ {
+ return UM.Theme.getColor("action_button_text");
+ }
+ }
+ font: UM.Theme.getFont("action_button")
+ text:
+ {
+ if(extruderModel == null)
+ {
+ return ""
+ }
+ if(extruderModel.isPreheating )
+ {
+ return catalog.i18nc("@button Cancel pre-heating", "Cancel")
+ } else
+ {
+ return catalog.i18nc("@button", "Pre-heat")
+ }
+ }
+ }
+ }
+ }
+
+ onClicked:
+ {
+ if (!extruderModel.isPreheating)
+ {
+ extruderModel.preheatHotend(preheatTemperatureInput.text, 900);
+ }
+ else
+ {
+ extruderModel.cancelPreheatHotend();
+ }
+ }
+
+ onHoveredChanged:
+ {
+ if (hovered)
+ {
+ base.showTooltip(
+ base,
+ {x: 0, y: preheatButton.mapToItem(base, 0, 0).y},
+ catalog.i18nc("@tooltip of pre-heat", "Heat the hotend in advance before printing. You can continue adjusting your print while it is heating, and you won't have to wait for the hotend to heat up when you're ready to print.")
+ );
+ }
+ else
+ {
+ base.hideTooltip();
+ }
+ }
+ }
+
Rectangle //Material colour indication.
{
id: materialColor
diff --git a/resources/qml/PrinterOutput/HeatedBedBox.qml b/resources/qml/PrinterOutput/HeatedBedBox.qml
index bc89da2251..9de66ad0be 100644
--- a/resources/qml/PrinterOutput/HeatedBedBox.qml
+++ b/resources/qml/PrinterOutput/HeatedBedBox.qml
@@ -1,3 +1,6 @@
+// Copyright (c) 2017 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.1
@@ -11,6 +14,7 @@ Item
implicitWidth: parent.width
height: visible ? UM.Theme.getSize("sidebar_extruder_box").height : 0
property var printerModel
+
Rectangle
{
color: UM.Theme.getColor("sidebar")
@@ -114,21 +118,24 @@ Item
{
return false; //Not allowed to do anything.
}
- if (connectedPrinter.jobState == "printing" || connectedPrinter.jobState == "pre_print" || connectedPrinter.jobState == "resuming" || connectedPrinter.jobState == "pausing" || connectedPrinter.jobState == "paused" || connectedPrinter.jobState == "error" || connectedPrinter.jobState == "offline")
+ if (connectedPrinter.activePrinter && connectedPrinter.activePrinter.activePrintJob)
{
- return false; //Printer is in a state where it can't react to pre-heating.
+ if((["printing", "pre_print", "resuming", "pausing", "paused", "error", "offline"]).indexOf(connectedPrinter.activePrinter.activePrintJob.state) != -1)
+ {
+ return false; //Printer is in a state where it can't react to pre-heating.
+ }
}
return true;
}
border.width: UM.Theme.getSize("default_lining").width
border.color: !enabled ? UM.Theme.getColor("setting_control_disabled_border") : preheatTemperatureInputMouseArea.containsMouse ? UM.Theme.getColor("setting_control_border_highlight") : UM.Theme.getColor("setting_control_border")
- anchors.left: parent.left
- anchors.leftMargin: UM.Theme.getSize("default_margin").width
+ anchors.right: preheatButton.left
+ anchors.rightMargin: UM.Theme.getSize("default_margin").width
anchors.bottom: parent.bottom
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
- width: UM.Theme.getSize("setting_control").width
- height: UM.Theme.getSize("setting_control").height
- visible: printerModel != null ? printerModel.canPreHeatBed: true
+ width: UM.Theme.getSize("monitor_preheat_temperature_control").width
+ height: UM.Theme.getSize("monitor_preheat_temperature_control").height
+ visible: printerModel != null ? enabled && printerModel.canPreHeatBed && !printerModel.isPreheating : true
Rectangle //Highlight of input field.
{
anchors.fill: parent
@@ -159,18 +166,29 @@ Item
}
}
}
+ Label
+ {
+ id: unit
+ anchors.right: parent.right
+ anchors.rightMargin: UM.Theme.getSize("setting_unit_margin").width
+ anchors.verticalCenter: parent.verticalCenter
+
+ text: "°C";
+ color: UM.Theme.getColor("setting_unit")
+ font: UM.Theme.getFont("default")
+ }
TextInput
{
id: preheatTemperatureInput
font: UM.Theme.getFont("default")
color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text")
selectByMouse: true
- maximumLength: 10
+ maximumLength: 5
enabled: parent.enabled
validator: RegExpValidator { regExp: /^-?[0-9]{0,9}[.,]?[0-9]{0,10}$/ } //Floating point regex.
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("setting_unit_margin").width
- anchors.right: parent.right
+ anchors.right: unit.left
anchors.verticalCenter: parent.verticalCenter
renderType: Text.NativeRendering
@@ -195,7 +213,7 @@ Item
}
}
- Button //The pre-heat button.
+ Button // The pre-heat button.
{
id: preheatButton
height: UM.Theme.getSize("setting_control").height
diff --git a/resources/qml/PrinterOutput/ManualPrinterControl.qml b/resources/qml/PrinterOutput/ManualPrinterControl.qml
index 43fa769fb5..70961a2eb2 100644
--- a/resources/qml/PrinterOutput/ManualPrinterControl.qml
+++ b/resources/qml/PrinterOutput/ManualPrinterControl.qml
@@ -9,7 +9,6 @@ import QtQuick.Layouts 1.1
import UM 1.2 as UM
import Cura 1.0 as Cura
-
Item
{
property var printerModel
@@ -125,7 +124,6 @@ Item
return true;
}
-
MonitorSection
{
label: catalog.i18nc("@label", "Printer control")
@@ -429,6 +427,120 @@ Item
}
}
+ Row
+ {
+ id: customCommandInputRow
+
+ width: base.width - 2 * UM.Theme.getSize("default_margin").width
+ height: childrenRect.height + UM.Theme.getSize("default_margin").width
+ anchors.left: parent.left
+ anchors.leftMargin: UM.Theme.getSize("default_margin").width
+
+ spacing: UM.Theme.getSize("default_margin").width
+
+ Label
+ {
+ text: catalog.i18nc("@label", "Send G-code")
+ color: UM.Theme.getColor("setting_control_text")
+ font: UM.Theme.getFont("default")
+
+ width: Math.floor(parent.width * 0.4) - UM.Theme.getSize("default_margin").width
+ height: UM.Theme.getSize("setting_control").height
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ Row
+ {
+ // Input field for custom G-code commands.
+ Rectangle
+ {
+ id: customCommandControl
+
+ // state
+ visible: printerModel != null ? printerModel.canSendRawGcode: true
+ enabled: {
+ if (printerModel == null) {
+ return false // Can't send custom commands if not connected.
+ }
+ if (!connectedPrinter.acceptsCommands) {
+ return false // Not allowed to do anything
+ }
+ if (connectedPrinter.jobState == "printing" || connectedPrinter.jobState == "pre_print" || connectedPrinter.jobState == "resuming" || connectedPrinter.jobState == "pausing" || connectedPrinter.jobState == "paused" || connectedPrinter.jobState == "error" || connectedPrinter.jobState == "offline") {
+ return false // Printer is in a state where it can't react to custom commands.
+ }
+ return true
+ }
+
+ // style
+ color: !enabled ? UM.Theme.getColor("setting_control_disabled") : UM.Theme.getColor("setting_validation_ok")
+ border.width: UM.Theme.getSize("default_lining").width
+ border.color: !enabled ? UM.Theme.getColor("setting_control_disabled_border") : customCommandControlMouseArea.containsMouse ? UM.Theme.getColor("setting_control_border_highlight") : UM.Theme.getColor("setting_control_border")
+
+ // size
+ width: UM.Theme.getSize("setting_control").width
+ height: UM.Theme.getSize("setting_control").height
+
+ // highlight
+ Rectangle
+ {
+ anchors.fill: parent
+ anchors.margins: UM.Theme.getSize("default_lining").width
+ color: UM.Theme.getColor("setting_control_highlight")
+ opacity: customCommandControl.hovered ? 1.0 : 0
+ }
+
+ // cursor hover popup
+ MouseArea
+ {
+ id: customCommandControlMouseArea
+ hoverEnabled: true
+ anchors.fill: parent
+ cursorShape: Qt.IBeamCursor
+
+ onHoveredChanged:
+ {
+ if (containsMouse) {
+ base.showTooltip(
+ base,
+ { x: 0, y: customCommandControlMouseArea.mapToItem(base, 0, 0).y },
+ catalog.i18nc("@tooltip of G-code command input", "Send a custom G-code command to the connected printer. Press 'enter' to send the command.")
+ )
+ } else {
+ base.hideTooltip()
+ }
+ }
+ }
+
+ TextInput
+ {
+ id: customCommandControlInput
+
+ // style
+ font: UM.Theme.getFont("default")
+ color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text")
+ selectByMouse: true
+ clip: true
+ enabled: parent.enabled
+ renderType: Text.NativeRendering
+
+ // anchors
+ anchors.left: parent.left
+ anchors.leftMargin: UM.Theme.getSize("setting_unit_margin").width
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+
+ // send the command when pressing enter
+ // we also clear the text field
+ Keys.onReturnPressed:
+ {
+ printerModel.sendRawCommand(customCommandControlInput.text)
+ customCommandControlInput.text = ""
+ }
+ }
+ }
+ }
+ }
+
ListModel
{
id: distancesModel
diff --git a/resources/qml/Settings/SettingExtruder.qml b/resources/qml/Settings/SettingExtruder.qml
index 17a0dd5eee..38b1c2cab0 100644
--- a/resources/qml/Settings/SettingExtruder.qml
+++ b/resources/qml/Settings/SettingExtruder.qml
@@ -17,14 +17,39 @@ SettingItem
id: control
anchors.fill: parent
- model: Cura.ExtrudersModel { onModelChanged: control.color = getItem(control.currentIndex).color }
+ model: Cura.ExtrudersModel
+ {
+ onModelChanged: {
+ control.color = getItem(control.currentIndex).color;
+ }
+ }
textRole: "name"
+ // knowing the extruder position, try to find the item index in the model
+ function getIndexByPosition(position)
+ {
+ for (var item_index in model.items)
+ {
+ var item = model.getItem(item_index)
+ if (item.index == position)
+ {
+ return item_index
+ }
+ }
+ return -1
+ }
+
onActivated:
{
- forceActiveFocus();
- propertyProvider.setPropertyValue("value", model.getItem(index).index);
+ if (model.getItem(index).enabled)
+ {
+ forceActiveFocus();
+ propertyProvider.setPropertyValue("value", model.getItem(index).index);
+ } else
+ {
+ currentIndex = propertyProvider.properties.value; // keep the old value
+ }
}
onActiveFocusChanged:
@@ -64,6 +89,23 @@ SettingItem
value: control.currentText != "" ? control.model.getItem(control.currentIndex).color : ""
}
+ Binding
+ {
+ target: control
+ property: "currentIndex"
+ value:
+ {
+ if(propertyProvider.properties.value == -1)
+ {
+ return control.getIndexByPosition(Cura.MachineManager.defaultExtruderPosition);
+ }
+ return propertyProvider.properties.value
+ }
+ // Sometimes when the value is already changed, the model is still being built.
+ // The when clause ensures that the current index is not updated when this happens.
+ when: control.model.items.length > 0
+ }
+
indicator: UM.RecolorImage
{
id: downArrow
@@ -173,7 +215,14 @@ SettingItem
{
text: model.name
renderType: Text.NativeRendering
- color: UM.Theme.getColor("setting_control_text")
+ color:
+ {
+ if (model.enabled) {
+ UM.Theme.getColor("setting_control_text")
+ } else {
+ UM.Theme.getColor("action_button_disabled_text");
+ }
+ }
font: UM.Theme.getFont("default")
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
diff --git a/resources/qml/Settings/SettingOptionalExtruder.qml b/resources/qml/Settings/SettingOptionalExtruder.qml
index f49b7035d7..a370ec6259 100644
--- a/resources/qml/Settings/SettingOptionalExtruder.qml
+++ b/resources/qml/Settings/SettingOptionalExtruder.qml
@@ -27,8 +27,19 @@ SettingItem
onActivated:
{
- forceActiveFocus();
- propertyProvider.setPropertyValue("value", model.getItem(index).index);
+ if (model.getItem(index).enabled)
+ {
+ forceActiveFocus();
+ propertyProvider.setPropertyValue("value", model.getItem(index).index);
+ } else
+ {
+ if (propertyProvider.properties.value == -1)
+ {
+ control.currentIndex = model.rowCount() - 1; // we know the last item is "Not overriden"
+ } else {
+ control.currentIndex = propertyProvider.properties.value; // revert to the old value
+ }
+ }
}
onActiveFocusChanged:
@@ -192,7 +203,14 @@ SettingItem
{
text: model.name
renderType: Text.NativeRendering
- color: UM.Theme.getColor("setting_control_text")
+ color:
+ {
+ if (model.enabled) {
+ UM.Theme.getColor("setting_control_text")
+ } else {
+ UM.Theme.getColor("action_button_disabled_text");
+ }
+ }
font: UM.Theme.getFont("default")
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
diff --git a/resources/qml/Settings/SettingTextField.qml b/resources/qml/Settings/SettingTextField.qml
index 4693bec50d..c2c04ce36c 100644
--- a/resources/qml/Settings/SettingTextField.qml
+++ b/resources/qml/Settings/SettingTextField.qml
@@ -13,11 +13,17 @@ SettingItem
property string textBeforeEdit
property bool textHasChanged
+ property bool focusGainedByClick: false
onFocusReceived:
{
textHasChanged = false;
textBeforeEdit = focusItem.text;
- focusItem.selectAll();
+
+ if(!focusGainedByClick)
+ {
+ // select all text when tabbing through fields (but not when selecting a field with the mouse)
+ focusItem.selectAll();
+ }
}
contents: Rectangle
@@ -93,14 +99,6 @@ SettingItem
font: UM.Theme.getFont("default")
}
- MouseArea
- {
- id: mouseArea
- anchors.fill: parent;
- //hoverEnabled: true;
- cursorShape: Qt.IBeamCursor
- }
-
TextInput
{
id: input
@@ -142,6 +140,7 @@ SettingItem
{
base.focusReceived();
}
+ base.focusGainedByClick = false;
}
color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text")
@@ -178,6 +177,22 @@ SettingItem
}
when: !input.activeFocus
}
+
+ MouseArea
+ {
+ id: mouseArea
+ anchors.fill: parent;
+
+ cursorShape: Qt.IBeamCursor
+
+ onPressed: {
+ if(!input.activeFocus) {
+ base.focusGainedByClick = true;
+ input.forceActiveFocus();
+ }
+ mouse.accepted = false;
+ }
+ }
}
}
}
diff --git a/resources/qml/Settings/SettingUnknown.qml b/resources/qml/Settings/SettingUnknown.qml
index 704964eda2..8eeb2fb6a0 100644
--- a/resources/qml/Settings/SettingUnknown.qml
+++ b/resources/qml/Settings/SettingUnknown.qml
@@ -11,9 +11,10 @@ SettingItem
contents: Label
{
anchors.fill: parent
- text: value + " " + unit;
- color: UM.Theme.getColor("setting_control_text")
-
- verticalAlignment: Qt.AlignVCenter
+ text: propertyProvider.properties.value + " " + unit
+ renderType: Text.NativeRendering
+ font: UM.Theme.getFont("default")
+ color: UM.Theme.getColor("text")
+ verticalAlignment: Text.AlignVCenter
}
}
diff --git a/resources/qml/Settings/SettingView.qml b/resources/qml/Settings/SettingView.qml
index 615e66277b..199db1bbaa 100644
--- a/resources/qml/Settings/SettingView.qml
+++ b/resources/qml/Settings/SettingView.qml
@@ -15,10 +15,11 @@ Item
{
id: base;
- property Action configureSettings;
- property bool findingSettings;
- signal showTooltip(Item item, point location, string text);
- signal hideTooltip();
+ property QtObject settingVisibilityPresetsModel: CuraApplication.getSettingVisibilityPresetsModel()
+ property Action configureSettings
+ property bool findingSettings
+ signal showTooltip(Item item, point location, string text)
+ signal hideTooltip()
Item
{
@@ -107,6 +108,45 @@ Item
}
}
+ ToolButton
+ {
+ id: settingVisibilityMenu
+
+ width: height
+ height: UM.Theme.getSize("setting_control").height
+ anchors
+ {
+ top: globalProfileRow.bottom
+ topMargin: UM.Theme.getSize("sidebar_margin").height
+ right: parent.right
+ rightMargin: UM.Theme.getSize("sidebar_margin").width
+ }
+ style: ButtonStyle
+ {
+ background: Item {
+ UM.RecolorImage {
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: UM.Theme.getSize("standard_arrow").width
+ height: UM.Theme.getSize("standard_arrow").height
+ sourceSize.width: width
+ sourceSize.height: width
+ color: control.enabled ? UM.Theme.getColor("setting_category_text") : UM.Theme.getColor("setting_category_disabled_text")
+ source: UM.Theme.getIcon("menu")
+ }
+ }
+ label: Label{}
+ }
+ menu: SettingVisibilityPresetsMenu
+ {
+ onShowAllSettings:
+ {
+ definitionsModel.setAllVisible(true);
+ filter.updateDefinitionModel();
+ }
+ }
+ }
+
Rectangle
{
id: filterContainer
@@ -132,9 +172,9 @@ Item
top: globalProfileRow.bottom
topMargin: UM.Theme.getSize("sidebar_margin").height
left: parent.left
- leftMargin: Math.round(UM.Theme.getSize("sidebar_margin").width)
- right: parent.right
- rightMargin: Math.round(UM.Theme.getSize("sidebar_margin").width)
+ leftMargin: UM.Theme.getSize("sidebar_margin").width
+ right: settingVisibilityMenu.left
+ rightMargin: Math.floor(UM.Theme.getSize("default_margin").width / 2)
}
height: visible ? UM.Theme.getSize("setting_control").height : 0
Behavior on height { NumberAnimation { duration: 100 } }
@@ -166,19 +206,7 @@ Item
findingSettings = (text.length > 0);
if(findingSettings != lastFindingSettings)
{
- if(findingSettings)
- {
- expandedCategories = definitionsModel.expanded.slice();
- definitionsModel.expanded = ["*"];
- definitionsModel.showAncestors = true;
- definitionsModel.showAll = true;
- }
- else
- {
- definitionsModel.expanded = expandedCategories;
- definitionsModel.showAncestors = false;
- definitionsModel.showAll = false;
- }
+ updateDefinitionModel();
lastFindingSettings = findingSettings;
}
}
@@ -187,6 +215,27 @@ Item
{
filter.text = "";
}
+
+ function updateDefinitionModel()
+ {
+ if(findingSettings)
+ {
+ expandedCategories = definitionsModel.expanded.slice();
+ definitionsModel.expanded = [""]; // keep categories closed while to prevent render while making settings visible one by one
+ definitionsModel.showAncestors = true;
+ definitionsModel.showAll = true;
+ definitionsModel.expanded = ["*"];
+ }
+ else
+ {
+ if(expandedCategories)
+ {
+ definitionsModel.expanded = expandedCategories;
+ }
+ definitionsModel.showAncestors = false;
+ definitionsModel.showAll = false;
+ }
+ }
}
MouseArea
@@ -209,7 +258,7 @@ Item
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
- anchors.rightMargin: Math.round(UM.Theme.getSize("sidebar_margin").width)
+ anchors.rightMargin: UM.Theme.getSize("default_margin").width
color: UM.Theme.getColor("setting_control_button")
hoverColor: UM.Theme.getColor("setting_control_button_hover")
@@ -374,7 +423,6 @@ Item
key: model.key ? model.key : ""
watchedProperties: [ "value", "enabled", "state", "validationState", "settable_per_extruder", "resolve" ]
storeIndex: 0
- // Due to the way setPropertyValue works, removeUnusedValue gives the correct output in case of resolve
removeUnusedValue: model.resolve == undefined
}
@@ -493,9 +541,17 @@ Item
MenuItem
{
//: Settings context menu action
- visible: !findingSettings;
+ visible: !findingSettings
text: catalog.i18nc("@action:menu", "Hide this setting");
- onTriggered: definitionsModel.hide(contextMenu.key);
+ onTriggered:
+ {
+ definitionsModel.hide(contextMenu.key);
+ // visible settings have changed, so we're no longer showing a preset
+ if (settingVisibilityPresetsModel.activePreset != "")
+ {
+ settingVisibilityPresetsModel.setActivePreset("custom");
+ }
+ }
}
MenuItem
{
@@ -511,7 +567,7 @@ Item
return catalog.i18nc("@action:menu", "Keep this setting visible");
}
}
- visible: findingSettings;
+ visible: findingSettings
onTriggered:
{
if (contextMenu.settingVisible)
@@ -522,12 +578,17 @@ Item
{
definitionsModel.show(contextMenu.key);
}
+ // visible settings have changed, so we're no longer showing a preset
+ if (settingVisibilityPresetsModel.activePreset != "")
+ {
+ settingVisibilityPresetsModel.setActivePreset("custom");
+ }
}
}
MenuItem
{
//: Settings context menu action
- text: catalog.i18nc("@action:menu", "Configure setting visiblity...");
+ text: catalog.i18nc("@action:menu", "Configure setting visibility...");
onTriggered: Cura.Actions.configureSettingVisibility.trigger(contextMenu);
}
diff --git a/resources/qml/Sidebar.qml b/resources/qml/Sidebar.qml
index e04d8607da..1c1bfca7c3 100644
--- a/resources/qml/Sidebar.qml
+++ b/resources/qml/Sidebar.qml
@@ -8,6 +8,7 @@ import QtQuick.Layouts 1.3
import UM 1.2 as UM
import Cura 1.0 as Cura
import "Menus"
+import "Menus/ConfigurationMenu"
Rectangle
{
@@ -18,6 +19,7 @@ Rectangle
property bool hideView: Cura.MachineManager.activeMachineName == ""
// Is there an output device for this printer?
+ property bool isNetworkPrinter: Cura.MachineManager.activeMachineNetworkKey != ""
property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0
property bool printerAcceptsCommands: printerConnected && Cura.MachineManager.printerOutputDevices[0].acceptsCommands
property var connectedPrinter: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null
@@ -64,11 +66,11 @@ Rectangle
function getPrettyTime(time)
{
- var hours = Math.round(time / 3600)
+ var hours = Math.floor(time / 3600)
time -= hours * 3600
- var minutes = Math.round(time / 60);
+ var minutes = Math.floor(time / 60);
time -= minutes * 60
- var seconds = Math.round(time);
+ var seconds = Math.floor(time);
var finalTime = strPadLeft(hours, "0", 2) + ':' + strPadLeft(minutes,'0',2)+ ':' + strPadLeft(seconds,'0',2);
return finalTime;
@@ -85,12 +87,34 @@ Rectangle
}
}
- MachineSelection {
+ MachineSelection
+ {
id: machineSelection
- width: base.width
+ width: base.width - configSelection.width - separator.width
+ height: UM.Theme.getSize("sidebar_header").height
+ anchors.top: base.top
+ anchors.left: parent.left
+ }
+
+ Rectangle
+ {
+ id: separator
+ visible: configSelection.visible
+ width: visible ? Math.round(UM.Theme.getSize("sidebar_lining_thin").height / 2) : 0
+ height: UM.Theme.getSize("sidebar_header").height
+ color: UM.Theme.getColor("sidebar_lining_thin")
+ anchors.left: machineSelection.right
+ }
+
+ ConfigurationSelection
+ {
+ id: configSelection
+ visible: isNetworkPrinter && printerConnected
+ width: visible ? Math.round(base.width * 0.15) : 0
height: UM.Theme.getSize("sidebar_header").height
anchors.top: base.top
anchors.right: parent.right
+ panelWidth: base.width
}
SidebarHeader {
diff --git a/resources/qml/SidebarHeader.qml b/resources/qml/SidebarHeader.qml
index 5d9cbe2ad1..1ba04c387e 100644
--- a/resources/qml/SidebarHeader.qml
+++ b/resources/qml/SidebarHeader.qml
@@ -16,6 +16,8 @@ Column
property int currentExtruderIndex: Cura.ExtruderManager.activeExtruderIndex;
property bool currentExtruderVisible: extrudersList.visible;
+ property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0
+ property bool hasManyPrinterTypes: printerConnected ? Cura.MachineManager.printerOutputDevices[0].connectedPrintersTypeCount.length > 1 : false
spacing: Math.round(UM.Theme.getSize("sidebar_margin").width * 0.9)
@@ -24,16 +26,66 @@ Column
Item
{
+ id: initialSeparator
anchors
{
left: parent.left
right: parent.right
}
- visible: extruderSelectionRow.visible
+ visible: printerTypeSelectionRow.visible || buildplateRow.visible || extruderSelectionRow.visible
height: UM.Theme.getSize("default_lining").height
width: height
}
+ // Printer Type Row
+ Item
+ {
+ id: printerTypeSelectionRow
+ height: UM.Theme.getSize("sidebar_setup").height
+ visible: printerConnected && hasManyPrinterTypes && !sidebar.monitoringPrint && !sidebar.hideSettings
+
+ anchors
+ {
+ left: parent.left
+ leftMargin: UM.Theme.getSize("sidebar_margin").width
+ right: parent.right
+ rightMargin: UM.Theme.getSize("sidebar_margin").width
+ }
+
+ Label
+ {
+ id: configurationLabel
+ text: catalog.i18nc("@label", "Printer type");
+ width: Math.round(parent.width * 0.4 - UM.Theme.getSize("default_margin").width)
+ height: parent.height
+ verticalAlignment: Text.AlignVCenter
+ font: UM.Theme.getFont("default");
+ color: UM.Theme.getColor("text");
+ }
+
+ ToolButton
+ {
+ id: printerTypeSelection
+ text: Cura.MachineManager.activeMachineDefinitionName
+ tooltip: Cura.MachineManager.activeMachineDefinitionName
+ height: UM.Theme.getSize("setting_control").height
+ width: Math.round(parent.width * 0.7) + UM.Theme.getSize("sidebar_margin").width
+ anchors.right: parent.right
+ style: UM.Theme.styles.sidebar_header_button
+ activeFocusOnPress: true;
+
+ menu: PrinterTypeMenu { }
+ }
+ }
+
+ Rectangle {
+ id: headerSeparator
+ width: parent.width
+ visible: printerTypeSelectionRow.visible
+ height: visible ? UM.Theme.getSize("sidebar_lining").height : 0
+ color: UM.Theme.getColor("sidebar_lining")
+ }
+
// Extruder Row
Item
{
@@ -91,26 +143,92 @@ Column
exclusiveGroup: extruderMenuGroup
checked: base.currentExtruderIndex == index
- onClicked:
+ property bool extruder_enabled: true
+
+ MouseArea
{
- forceActiveFocus() // Changing focus applies the currently-being-typed values so it can change the displayed setting values.
- Cura.ExtruderManager.setActiveExtruderIndex(index);
+ anchors.fill: parent
+ acceptedButtons: Qt.LeftButton | Qt.RightButton
+ onClicked: {
+ switch (mouse.button) {
+ case Qt.LeftButton:
+ forceActiveFocus(); // Changing focus applies the currently-being-typed values so it can change the displayed setting values.
+ Cura.ExtruderManager.setActiveExtruderIndex(index);
+ break;
+ case Qt.RightButton:
+ extruder_enabled = Cura.MachineManager.getExtruder(model.index).isEnabled
+ extruderMenu.popup();
+ break;
+ }
+
+ }
+ }
+
+ Menu
+ {
+ id: extruderMenu
+
+ MenuItem {
+ text: catalog.i18nc("@action:inmenu", "Enable Extruder")
+ onTriggered: Cura.MachineManager.setExtruderEnabled(model.index, true)
+ visible: !extruder_enabled // using an intermediate variable prevents an empty popup that occured now and then
+ }
+
+ MenuItem {
+ text: catalog.i18nc("@action:inmenu", "Disable Extruder")
+ onTriggered: Cura.MachineManager.setExtruderEnabled(model.index, false)
+ visible: extruder_enabled
+ enabled: Cura.MachineManager.numberExtrudersEnabled > 1
+ }
}
style: ButtonStyle
{
background: Item
{
+ function buttonBackgroundColor(index)
+ {
+ var extruder = Cura.MachineManager.getExtruder(index)
+ if (extruder.isEnabled) {
+ return (control.checked || control.pressed) ? UM.Theme.getColor("action_button_active") :
+ control.hovered ? UM.Theme.getColor("action_button_hovered") :
+ UM.Theme.getColor("action_button")
+ } else {
+ return UM.Theme.getColor("action_button_disabled")
+ }
+ }
+
+ function buttonBorderColor(index)
+ {
+ var extruder = Cura.MachineManager.getExtruder(index)
+ if (extruder.isEnabled) {
+ return (control.checked || control.pressed) ? UM.Theme.getColor("action_button_active_border") :
+ control.hovered ? UM.Theme.getColor("action_button_hovered_border") :
+ UM.Theme.getColor("action_button_border")
+ } else {
+ return UM.Theme.getColor("action_button_disabled_border")
+ }
+ }
+
+ function buttonColor(index) {
+ var extruder = Cura.MachineManager.getExtruder(index);
+ if (extruder.isEnabled)
+ {
+ return (
+ control.checked || control.pressed) ? UM.Theme.getColor("action_button_active_text") :
+ control.hovered ? UM.Theme.getColor("action_button_hovered_text") :
+ UM.Theme.getColor("action_button_text");
+ } else {
+ return UM.Theme.getColor("action_button_disabled_text");
+ }
+ }
+
Rectangle
{
anchors.fill: parent
border.width: control.checked ? UM.Theme.getSize("default_lining").width * 2 : UM.Theme.getSize("default_lining").width
- border.color: (control.checked || control.pressed) ? UM.Theme.getColor("action_button_active_border") :
- control.hovered ? UM.Theme.getColor("action_button_hovered_border") :
- UM.Theme.getColor("action_button_border")
- color: (control.checked || control.pressed) ? UM.Theme.getColor("action_button_active") :
- control.hovered ? UM.Theme.getColor("action_button_hovered") :
- UM.Theme.getColor("action_button")
+ border.color: buttonBorderColor(index)
+ color: buttonBackgroundColor(index)
Behavior on color { ColorAnimation { duration: 50; } }
}
@@ -118,6 +236,7 @@ Column
{
id: extruderButtonFace
anchors.centerIn: parent
+
width: {
var extruderTextWidth = extruderStaticText.visible ? extruderStaticText.width : 0;
var iconWidth = extruderIconItem.width;
@@ -131,9 +250,7 @@ Column
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
- color: (control.checked || control.pressed) ? UM.Theme.getColor("action_button_active_text") :
- control.hovered ? UM.Theme.getColor("action_button_hovered_text") :
- UM.Theme.getColor("action_button_text")
+ color: buttonColor(index)
font: UM.Theme.getFont("large_nonbold")
text: catalog.i18nc("@label", "Extruder")
@@ -176,9 +293,7 @@ Column
id: extruderNumberText
anchors.centerIn: parent
text: index + 1;
- color: (control.checked || control.pressed) ? UM.Theme.getColor("action_button_active_text") :
- control.hovered ? UM.Theme.getColor("action_button_hovered_text") :
- UM.Theme.getColor("action_button_text")
+ color: buttonColor(index)
font: UM.Theme.getFont("default_bold")
}
@@ -221,7 +336,7 @@ Column
id: variantRowSpacer
height: Math.round(UM.Theme.getSize("sidebar_margin").height / 4)
width: height
- visible: !extruderSelectionRow.visible
+ visible: !extruderSelectionRow.visible && !initialSeparator.visible
}
// Material Row
@@ -244,6 +359,8 @@ Column
id: materialLabel
text: catalog.i18nc("@label", "Material");
width: Math.round(parent.width * 0.45 - UM.Theme.getSize("default_margin").width)
+ height: parent.height
+ verticalAlignment: Text.AlignVCenter
font: UM.Theme.getFont("default");
color: UM.Theme.getColor("text");
}
@@ -252,15 +369,9 @@ Column
{
id: materialSelection
- property var currentRootMaterialName:
- {
- var materials = Cura.MachineManager.currentRootMaterialName;
- var materialName = "";
- if (base.currentExtruderIndex in materials) {
- materialName = materials[base.currentExtruderIndex];
- }
- return materialName;
- }
+ property var activeExtruder: Cura.MachineManager.activeStack
+ property var hasActiveExtruder: activeExtruder != null
+ property var currentRootMaterialName: hasActiveExtruder ? activeExtruder.material.name : ""
text: currentRootMaterialName
tooltip: currentRootMaterialName
@@ -279,7 +390,11 @@ Column
property var valueWarning: ! Cura.MachineManager.isActiveQualitySupported
function isMaterialSupported () {
- return Cura.ContainerManager.getContainerMetaDataEntry(Cura.MachineManager.activeMaterialId, "compatible") == "True"
+ if (!hasActiveExtruder)
+ {
+ return false;
+ }
+ return Cura.ContainerManager.getContainerMetaDataEntry(activeExtruder.material.id, "compatible") == "True"
}
}
}
@@ -304,6 +419,8 @@ Column
id: variantLabel
text: Cura.MachineManager.activeDefinitionVariantsName;
width: Math.round(parent.width * 0.45 - UM.Theme.getSize("default_margin").width)
+ height: parent.height
+ verticalAlignment: Text.AlignVCenter
font: UM.Theme.getFont("default");
color: UM.Theme.getColor("text");
}
@@ -324,17 +441,14 @@ Column
}
}
- //Buildplate row separator
Rectangle {
- id: separator
-
+ id: buildplateSeparator
+ anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("sidebar_margin").width
- anchors.rightMargin: UM.Theme.getSize("sidebar_margin").width
- anchors.horizontalCenter: parent.horizontalCenter
+ width: parent.width - 2 * UM.Theme.getSize("sidebar_margin").width
visible: buildplateRow.visible
- width: parent.width - UM.Theme.getSize("sidebar_margin").width * 2
- height: visible ? Math.floor(UM.Theme.getSize("sidebar_lining_thin").height / 2) : 0
- color: UM.Theme.getColor("sidebar_lining_thin")
+ height: visible ? UM.Theme.getSize("sidebar_lining_thin").height : 0
+ color: UM.Theme.getColor("sidebar_lining")
}
//Buildplate row
@@ -357,6 +471,8 @@ Column
id: bulidplateLabel
text: catalog.i18nc("@label", "Build plate");
width: Math.floor(parent.width * 0.45 - UM.Theme.getSize("default_margin").width)
+ height: parent.height
+ verticalAlignment: Text.AlignVCenter
font: UM.Theme.getFont("default");
color: UM.Theme.getColor("text");
}
@@ -408,6 +524,8 @@ Column
source: UM.Theme.getIcon("warning")
width: UM.Theme.getSize("section_icon").width
height: UM.Theme.getSize("section_icon").height
+ sourceSize.width: width
+ sourceSize.height: height
color: UM.Theme.getColor("material_compatibility_warning")
visible: !Cura.MachineManager.isCurrentSetupSupported
}
@@ -429,9 +547,7 @@ Column
hoverEnabled: true
onClicked: {
// open the material URL with web browser
- var version = UM.Application.version;
- var machineName = Cura.MachineManager.activeMachine.definition.id;
- var url = "https://ultimaker.com/materialcompatibility/" + version + "/" + machineName + "?utm_source=cura&utm_medium=software&utm_campaign=resources";
+ var url = "https://ultimaker.com/incoming-links/cura/material-compatibilty"
Qt.openUrlExternally(url);
}
onEntered: {
diff --git a/resources/qml/SidebarSimple.qml b/resources/qml/SidebarSimple.qml
index cef6f38b4b..c32b8ba957 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: Cura.ExtruderManager.activeExtruderStackId || machineExtruderCount.properties.value == 1
+ property bool settingsEnabled: Cura.ExtruderManager.activeExtruderStackId || extrudersEnabledCount.properties.value == 1
Component.onCompleted: PrintInformation.enabled = true
Component.onDestruction: PrintInformation.enabled = false
@@ -67,10 +67,8 @@ Item
Connections
{
- target: Cura.MachineManager
- onActiveQualityChanged: qualityModel.update()
- onActiveMaterialChanged: qualityModel.update()
- onActiveVariantChanged: qualityModel.update()
+ target: Cura.QualityProfilesDropDownMenuModel
+ onItemsChanged: qualityModel.update()
}
Connections {
@@ -113,7 +111,6 @@ Item
// Set selected value
if (Cura.MachineManager.activeQualityType == qualityItem.quality_type) {
-
// set to -1 when switching to user created profile so all ticks are clickable
if (Cura.SimpleModeSettingsManager.isProfileUserCreated) {
qualityModel.qualitySliderActiveIndex = -1
@@ -245,6 +242,81 @@ Item
anchors.top: parent.top
anchors.topMargin: UM.Theme.getSize("sidebar_margin").height
+ // This Item is used only for tooltip, for slider area which is unavailable
+ Item
+ {
+ function showTooltip (showTooltip)
+ {
+ if (showTooltip) {
+ var content = catalog.i18nc("@tooltip", "This quality profile is not available for you current material and nozzle configuration. Please change these to enable this quality profile")
+ base.showTooltip(qualityRow, Qt.point(-UM.Theme.getSize("sidebar_margin").width, customisedSettings.height), content)
+ }
+ else {
+ base.hideTooltip()
+ }
+ }
+
+ id: unavailableLineToolTip
+ height: 20 // hovered area height
+ z: parent.z + 1 // should be higher, otherwise the area can be hovered
+ x: 0
+ anchors.verticalCenter: qualitySlider.verticalCenter
+
+ Rectangle
+ {
+ id: leftArea
+ width:
+ {
+ if (qualityModel.availableTotalTicks == 0) {
+ return qualityModel.qualitySliderStepWidth * qualityModel.totalTicks
+ }
+ return qualityModel.qualitySliderStepWidth * qualityModel.qualitySliderAvailableMin - 10
+ }
+ height: parent.height
+ color: "transparent"
+
+ MouseArea
+ {
+ anchors.fill: parent
+ hoverEnabled: true
+ enabled: Cura.SimpleModeSettingsManager.isProfileUserCreated == false
+ onEntered: unavailableLineToolTip.showTooltip(true)
+ onExited: unavailableLineToolTip.showTooltip(false)
+ }
+ }
+
+ Rectangle
+ {
+ id: rightArea
+ width: {
+ if(qualityModel.availableTotalTicks == 0)
+ return 0
+
+ return qualityModel.qualitySliderMarginRight - 10
+ }
+ height: parent.height
+ color: "transparent"
+ x: {
+ if (qualityModel.availableTotalTicks == 0) {
+ return 0
+ }
+
+ var leftUnavailableArea = qualityModel.qualitySliderStepWidth * qualityModel.qualitySliderAvailableMin
+ var totalGap = qualityModel.qualitySliderStepWidth * (qualityModel.availableTotalTicks -1) + leftUnavailableArea + 10
+
+ return totalGap
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ hoverEnabled: true
+ enabled: Cura.SimpleModeSettingsManager.isProfileUserCreated == false
+ onEntered: unavailableLineToolTip.showTooltip(true)
+ onExited: unavailableLineToolTip.showTooltip(false)
+ }
+ }
+ }
+
// Draw Unavailable line
Rectangle
{
@@ -401,18 +473,7 @@ Item
onClicked:
{
// if the current profile is user-created, switch to a built-in quality
- if (Cura.SimpleModeSettingsManager.isProfileUserCreated)
- {
- if (Cura.QualityProfilesDropDownMenuModel.rowCount() > 0)
- {
- var item = Cura.QualityProfilesDropDownMenuModel.getItem(0);
- Cura.MachineManager.activeQualityGroup = item.quality_group;
- }
- }
- if (Cura.SimpleModeSettingsManager.isProfileCustomized)
- {
- discardOrKeepProfileChangesDialog.show()
- }
+ Cura.MachineManager.resetToUseDefaultQuality()
}
onEntered:
{
@@ -518,7 +579,14 @@ Item
// Update the slider value to represent the rounded value
infillSlider.value = roundedSliderValue
- Cura.MachineManager.setSettingForAllExtruders("infill_sparse_density", "value", roundedSliderValue)
+ // Update value only if the Recomended mode is Active,
+ // Otherwise if I change the value in the Custom mode the Recomended view will try to repeat
+ // same operation
+ var active_mode = UM.Preferences.getValue("cura/active_mode")
+
+ if (active_mode == 0 || active_mode == "simple") {
+ Cura.MachineManager.setSettingForAllExtruders("infill_sparse_density", "value", roundedSliderValue)
+ }
}
style: SliderStyle
@@ -788,25 +856,10 @@ Item
}
}
- Label
- {
- id: supportExtruderLabel
- visible: supportExtruderCombobox.visible
- anchors.left: parent.left
- anchors.leftMargin: UM.Theme.getSize("sidebar_margin").width
- anchors.right: infillCellLeft.right
- anchors.rightMargin: UM.Theme.getSize("sidebar_margin").width
- anchors.verticalCenter: supportExtruderCombobox.verticalCenter
- text: catalog.i18nc("@label", "Support Extruder");
- font: UM.Theme.getFont("default");
- color: UM.Theme.getColor("text");
- elide: Text.ElideRight
- }
-
ComboBox
{
id: supportExtruderCombobox
- visible: enableSupportCheckBox.visible && (supportEnabled.properties.value == "True") && (machineExtruderCount.properties.value > 1)
+ visible: enableSupportCheckBox.visible && (supportEnabled.properties.value == "True") && (extrudersEnabledCount.properties.value > 1)
model: extruderModel
property string color_override: "" // for manually setting values
@@ -820,11 +873,12 @@ Item
textRole: "text" // this solves that the combobox isn't populated in the first time Cura is started
- anchors.top: enableSupportCheckBox.bottom
- anchors.topMargin: ((supportEnabled.properties.value === "True") && (machineExtruderCount.properties.value > 1)) ? UM.Theme.getSize("sidebar_margin").height : 0
- anchors.left: infillCellRight.left
+ anchors.top: enableSupportCheckBox.top
+ //anchors.topMargin: ((supportEnabled.properties.value === "True") && (machineExtruderCount.properties.value > 1)) ? UM.Theme.getSize("sidebar_margin").height : 0
+ anchors.left: enableSupportCheckBox.right
+ anchors.leftMargin: Math.round(UM.Theme.getSize("sidebar_margin").width / 2)
- width: Math.round(UM.Theme.getSize("sidebar").width * .55)
+ width: Math.round(UM.Theme.getSize("sidebar").width * .55) - Math.round(UM.Theme.getSize("sidebar_margin").width / 2) - enableSupportCheckBox.width
height: ((supportEnabled.properties.value == "True") && (machineExtruderCount.properties.value > 1)) ? UM.Theme.getSize("setting_control").height : 0
Behavior on height { NumberAnimation { duration: 100 } }
@@ -833,7 +887,23 @@ Item
enabled: base.settingsEnabled
property alias _hovered: supportExtruderMouseArea.containsMouse
- currentIndex: supportExtruderNr.properties !== null ? parseFloat(supportExtruderNr.properties.value) : 0
+ currentIndex:
+ {
+ if (supportExtruderNr.properties == null)
+ {
+ return Cura.MachineManager.defaultExtruderPosition;
+ }
+ else
+ {
+ var extruder = parseInt(supportExtruderNr.properties.value);
+ if ( extruder === -1)
+ {
+ return Cura.MachineManager.defaultExtruderPosition;
+ }
+ return extruder;
+ }
+ }
+
onActivated:
{
// Send the extruder nr as a string.
@@ -891,7 +961,7 @@ Item
id: adhesionCheckBox
property alias _hovered: adhesionMouseArea.containsMouse
- anchors.top: enableSupportCheckBox.visible ? supportExtruderCombobox.bottom : infillCellRight.bottom
+ anchors.top: enableSupportCheckBox.bottom
anchors.topMargin: UM.Theme.getSize("sidebar_margin").height
anchors.left: infillCellRight.left
@@ -1022,9 +1092,9 @@ Item
UM.SettingPropertyProvider
{
- id: machineExtruderCount
+ id: extrudersEnabledCount
containerStackId: Cura.MachineManager.activeMachineId
- key: "machine_extruder_count"
+ key: "extruders_enabled_count"
watchedProperties: [ "value" ]
storeIndex: 0
}
diff --git a/resources/qml/Topbar.qml b/resources/qml/Topbar.qml
index 950b9ec12d..69d27d483a 100644
--- a/resources/qml/Topbar.qml
+++ b/resources/qml/Topbar.qml
@@ -150,7 +150,7 @@ Rectangle
visible: base.width - allItemsWidth - 1 * this.width > 0
}
- // #5 Left view
+ // #5 Right view
Button
{
iconSource: UM.Theme.getIcon("view_right")
diff --git a/resources/qml/WorkspaceSummaryDialog.qml b/resources/qml/WorkspaceSummaryDialog.qml
index 1cfe36d14b..4d15860257 100644
--- a/resources/qml/WorkspaceSummaryDialog.qml
+++ b/resources/qml/WorkspaceSummaryDialog.qml
@@ -101,7 +101,7 @@ UM.Dialog
}
Label
{
- text: Cura.MachineManager.activeMachine.definition.name
+ text: (Cura.MachineManager.activeMachine == null) ? "" : Cura.MachineManager.activeMachine.definition.name
width: (parent.width / 3) | 0
}
}
@@ -111,12 +111,12 @@ UM.Dialog
height: childrenRect.height
Label
{
- text: catalog.i18nc("@action:label", "Name")
+ text: catalog.i18nc("@action:label", Cura.MachineManager.activeMachineNetworkGroupName != "" ? "Printer Group" : "Name")
width: (parent.width / 3) | 0
}
Label
{
- text: Cura.MachineManager.activeMachineName
+ text: Cura.MachineManager.activeMachineNetworkGroupName != "" ? Cura.MachineManager.activeMachineNetworkGroupName : Cura.MachineManager.activeMachineName
width: (parent.width / 3) | 0
}
}
@@ -173,7 +173,7 @@ UM.Dialog
}
Label
{
- text: Cura.MachineManager.activeVariantNames[modelData] + ", " + Cura.MachineManager.currentRootMaterialName[modelData]
+ text: Cura.MachineManager.activeVariantNames[modelData] + ", " + Cura.MachineManager.getExtruder(modelData).material.name
width: (parent.width / 3) | 0
}
}
diff --git a/resources/quality/abax_pri3/apri3_pla_fast.inst.cfg b/resources/quality/abax_pri3/apri3_pla_fast.inst.cfg
index 7d1c1bf588..50ff90670f 100644
--- a/resources/quality/abax_pri3/apri3_pla_fast.inst.cfg
+++ b/resources/quality/abax_pri3/apri3_pla_fast.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = abax_pri3
diff --git a/resources/quality/abax_pri3/apri3_pla_high.inst.cfg b/resources/quality/abax_pri3/apri3_pla_high.inst.cfg
index 46a4178dd9..0bcd5ef77d 100644
--- a/resources/quality/abax_pri3/apri3_pla_high.inst.cfg
+++ b/resources/quality/abax_pri3/apri3_pla_high.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fine
definition = abax_pri3
diff --git a/resources/quality/abax_pri3/apri3_pla_normal.inst.cfg b/resources/quality/abax_pri3/apri3_pla_normal.inst.cfg
index 3f6f36cfe6..5ef275652d 100644
--- a/resources/quality/abax_pri3/apri3_pla_normal.inst.cfg
+++ b/resources/quality/abax_pri3/apri3_pla_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = abax_pri3
diff --git a/resources/quality/abax_pri5/apri5_pla_fast.inst.cfg b/resources/quality/abax_pri5/apri5_pla_fast.inst.cfg
index 517c767ac5..922da8e88e 100644
--- a/resources/quality/abax_pri5/apri5_pla_fast.inst.cfg
+++ b/resources/quality/abax_pri5/apri5_pla_fast.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = abax_pri5
diff --git a/resources/quality/abax_pri5/apri5_pla_high.inst.cfg b/resources/quality/abax_pri5/apri5_pla_high.inst.cfg
index 01699e39f6..2b1c6b017b 100644
--- a/resources/quality/abax_pri5/apri5_pla_high.inst.cfg
+++ b/resources/quality/abax_pri5/apri5_pla_high.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fine
definition = abax_pri5
diff --git a/resources/quality/abax_pri5/apri5_pla_normal.inst.cfg b/resources/quality/abax_pri5/apri5_pla_normal.inst.cfg
index ea1023dc43..23b21f597b 100644
--- a/resources/quality/abax_pri5/apri5_pla_normal.inst.cfg
+++ b/resources/quality/abax_pri5/apri5_pla_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = abax_pri5
diff --git a/resources/quality/abax_titan/atitan_pla_fast.inst.cfg b/resources/quality/abax_titan/atitan_pla_fast.inst.cfg
index ae489c3792..5d935a915a 100644
--- a/resources/quality/abax_titan/atitan_pla_fast.inst.cfg
+++ b/resources/quality/abax_titan/atitan_pla_fast.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = abax_titan
diff --git a/resources/quality/abax_titan/atitan_pla_high.inst.cfg b/resources/quality/abax_titan/atitan_pla_high.inst.cfg
index f9bf350814..8bd45034e3 100644
--- a/resources/quality/abax_titan/atitan_pla_high.inst.cfg
+++ b/resources/quality/abax_titan/atitan_pla_high.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fine
definition = abax_titan
diff --git a/resources/quality/abax_titan/atitan_pla_normal.inst.cfg b/resources/quality/abax_titan/atitan_pla_normal.inst.cfg
index c73d6901fb..081e5ad977 100644
--- a/resources/quality/abax_titan/atitan_pla_normal.inst.cfg
+++ b/resources/quality/abax_titan/atitan_pla_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = abax_titan
diff --git a/resources/quality/anycubic_i3_mega/anycubic_i3_mega_draft.inst.cfg b/resources/quality/anycubic_i3_mega/anycubic_i3_mega_draft.inst.cfg
index caa6d8edb6..821205ca69 100644
--- a/resources/quality/anycubic_i3_mega/anycubic_i3_mega_draft.inst.cfg
+++ b/resources/quality/anycubic_i3_mega/anycubic_i3_mega_draft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Draft
definition = anycubic_i3_mega
diff --git a/resources/quality/anycubic_i3_mega/anycubic_i3_mega_high.inst.cfg b/resources/quality/anycubic_i3_mega/anycubic_i3_mega_high.inst.cfg
index 5d6f8d9013..e686ce50ac 100644
--- a/resources/quality/anycubic_i3_mega/anycubic_i3_mega_high.inst.cfg
+++ b/resources/quality/anycubic_i3_mega/anycubic_i3_mega_high.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High
definition = anycubic_i3_mega
diff --git a/resources/quality/anycubic_i3_mega/anycubic_i3_mega_normal.inst.cfg b/resources/quality/anycubic_i3_mega/anycubic_i3_mega_normal.inst.cfg
index ca25afa424..3e855c1bbc 100644
--- a/resources/quality/anycubic_i3_mega/anycubic_i3_mega_normal.inst.cfg
+++ b/resources/quality/anycubic_i3_mega/anycubic_i3_mega_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = anycubic_i3_mega
diff --git a/resources/quality/builder_premium/bp_BVOH_Coarse_Quality.inst.cfg b/resources/quality/builder_premium/bp_BVOH_Coarse_Quality.inst.cfg
index d83f2a1ccd..c5a92e3c93 100644
--- a/resources/quality/builder_premium/bp_BVOH_Coarse_Quality.inst.cfg
+++ b/resources/quality/builder_premium/bp_BVOH_Coarse_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Coarse
definition = builder_premium_small
diff --git a/resources/quality/builder_premium/bp_BVOH_High_Quality.inst.cfg b/resources/quality/builder_premium/bp_BVOH_High_Quality.inst.cfg
index f029a0206f..72ce4f3555 100644
--- a/resources/quality/builder_premium/bp_BVOH_High_Quality.inst.cfg
+++ b/resources/quality/builder_premium/bp_BVOH_High_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High Quality
definition = builder_premium_small
diff --git a/resources/quality/builder_premium/bp_BVOH_Normal_Quality.inst.cfg b/resources/quality/builder_premium/bp_BVOH_Normal_Quality.inst.cfg
index 640812b37d..daa1d8bc78 100644
--- a/resources/quality/builder_premium/bp_BVOH_Normal_Quality.inst.cfg
+++ b/resources/quality/builder_premium/bp_BVOH_Normal_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = builder_premium_small
diff --git a/resources/quality/builder_premium/bp_Innoflex60_Coarse_Quality.inst.cfg b/resources/quality/builder_premium/bp_Innoflex60_Coarse_Quality.inst.cfg
index 905cf50518..827c726071 100644
--- a/resources/quality/builder_premium/bp_Innoflex60_Coarse_Quality.inst.cfg
+++ b/resources/quality/builder_premium/bp_Innoflex60_Coarse_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Coarse
definition = builder_premium_small
diff --git a/resources/quality/builder_premium/bp_Innoflex60_High_Quality.inst.cfg b/resources/quality/builder_premium/bp_Innoflex60_High_Quality.inst.cfg
index 835ce04d61..4c84d0ddfe 100644
--- a/resources/quality/builder_premium/bp_Innoflex60_High_Quality.inst.cfg
+++ b/resources/quality/builder_premium/bp_Innoflex60_High_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High Quality
definition = builder_premium_small
diff --git a/resources/quality/builder_premium/bp_Innoflex60_Normal_Quality.inst.cfg b/resources/quality/builder_premium/bp_Innoflex60_Normal_Quality.inst.cfg
index ba176dbc5c..23813f85b9 100644
--- a/resources/quality/builder_premium/bp_Innoflex60_Normal_Quality.inst.cfg
+++ b/resources/quality/builder_premium/bp_Innoflex60_Normal_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = builder_premium_small
diff --git a/resources/quality/builder_premium/bp_PET_Coarse_Quality.inst.cfg b/resources/quality/builder_premium/bp_PET_Coarse_Quality.inst.cfg
index 736defd5c6..da0d40393c 100644
--- a/resources/quality/builder_premium/bp_PET_Coarse_Quality.inst.cfg
+++ b/resources/quality/builder_premium/bp_PET_Coarse_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Coarse
definition = builder_premium_small
diff --git a/resources/quality/builder_premium/bp_PET_High_Quality.inst.cfg b/resources/quality/builder_premium/bp_PET_High_Quality.inst.cfg
index 82c7fa7baf..b78a5a29d7 100644
--- a/resources/quality/builder_premium/bp_PET_High_Quality.inst.cfg
+++ b/resources/quality/builder_premium/bp_PET_High_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High Quality
definition = builder_premium_small
diff --git a/resources/quality/builder_premium/bp_PET_Normal_Quality.inst.cfg b/resources/quality/builder_premium/bp_PET_Normal_Quality.inst.cfg
index b1e9ff91df..600fbed476 100644
--- a/resources/quality/builder_premium/bp_PET_Normal_Quality.inst.cfg
+++ b/resources/quality/builder_premium/bp_PET_Normal_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = builder_premium_small
diff --git a/resources/quality/builder_premium/bp_PLA_Coarse_Quality.inst.cfg b/resources/quality/builder_premium/bp_PLA_Coarse_Quality.inst.cfg
index 9b8078e266..d1c1d4d563 100644
--- a/resources/quality/builder_premium/bp_PLA_Coarse_Quality.inst.cfg
+++ b/resources/quality/builder_premium/bp_PLA_Coarse_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Coarse
definition = builder_premium_small
diff --git a/resources/quality/builder_premium/bp_PLA_High_Quality.inst.cfg b/resources/quality/builder_premium/bp_PLA_High_Quality.inst.cfg
index 2bbfb02e0d..1063342b89 100644
--- a/resources/quality/builder_premium/bp_PLA_High_Quality.inst.cfg
+++ b/resources/quality/builder_premium/bp_PLA_High_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High Quality
definition = builder_premium_small
diff --git a/resources/quality/builder_premium/bp_PLA_Normal_Quality.inst.cfg b/resources/quality/builder_premium/bp_PLA_Normal_Quality.inst.cfg
index b77ac747a8..6612c704f3 100644
--- a/resources/quality/builder_premium/bp_PLA_Normal_Quality.inst.cfg
+++ b/resources/quality/builder_premium/bp_PLA_Normal_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = builder_premium_small
diff --git a/resources/quality/builder_premium/bp_PVA_Coarse_Quality.inst.cfg b/resources/quality/builder_premium/bp_PVA_Coarse_Quality.inst.cfg
index f626604f70..2717ffe998 100644
--- a/resources/quality/builder_premium/bp_PVA_Coarse_Quality.inst.cfg
+++ b/resources/quality/builder_premium/bp_PVA_Coarse_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Coarse
definition = builder_premium_small
diff --git a/resources/quality/builder_premium/bp_PVA_High_Quality.inst.cfg b/resources/quality/builder_premium/bp_PVA_High_Quality.inst.cfg
index caf0bd4bd7..3ab782af5c 100644
--- a/resources/quality/builder_premium/bp_PVA_High_Quality.inst.cfg
+++ b/resources/quality/builder_premium/bp_PVA_High_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High Quality
definition = builder_premium_small
diff --git a/resources/quality/builder_premium/bp_PVA_Normal_Quality.inst.cfg b/resources/quality/builder_premium/bp_PVA_Normal_Quality.inst.cfg
index 4f08010a6f..fe24e976c7 100644
--- a/resources/quality/builder_premium/bp_PVA_Normal_Quality.inst.cfg
+++ b/resources/quality/builder_premium/bp_PVA_Normal_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = builder_premium_small
diff --git a/resources/quality/builder_premium/bp_global_Coarse_Quality.inst.cfg b/resources/quality/builder_premium/bp_global_Coarse_Quality.inst.cfg
index c9b2b3a654..708a135847 100644
--- a/resources/quality/builder_premium/bp_global_Coarse_Quality.inst.cfg
+++ b/resources/quality/builder_premium/bp_global_Coarse_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Coarse
definition = builder_premium_small
diff --git a/resources/quality/builder_premium/bp_global_High_Quality.inst.cfg b/resources/quality/builder_premium/bp_global_High_Quality.inst.cfg
index efad0f96e5..0c96206a7d 100644
--- a/resources/quality/builder_premium/bp_global_High_Quality.inst.cfg
+++ b/resources/quality/builder_premium/bp_global_High_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High Quality
definition = builder_premium_small
diff --git a/resources/quality/builder_premium/bp_global_Normal_Quality.inst.cfg b/resources/quality/builder_premium/bp_global_Normal_Quality.inst.cfg
index 69c6214aa8..833e7e8905 100644
--- a/resources/quality/builder_premium/bp_global_Normal_Quality.inst.cfg
+++ b/resources/quality/builder_premium/bp_global_Normal_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = builder_premium_small
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 82cbed79e3..5e713275c6 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High
definition = cartesio
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 4c8da554d3..d35dcf1c18 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = cartesio
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 1e068c97c7..06ef74108b 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High
definition = cartesio
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 7019eb7a5b..8e5b6aadc0 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = cartesio
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 7b12575e69..5da823a2d8 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Coarse
definition = cartesio
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 bc25bf8d00..267857cff1 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Coarse
definition = cartesio
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 b8ca55fa6e..e3219d145c 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High
definition = cartesio
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 409e0f9adf..111bef11dc 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = cartesio
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
index 11d541831d..35dddeca91 100644
--- a/resources/quality/cartesio/arnitel/cartesio_0.4_arnitel2045_high.inst.cfg
+++ b/resources/quality/cartesio/arnitel/cartesio_0.4_arnitel2045_high.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High
definition = cartesio
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
index d6a972b1cd..3234881c72 100644
--- a/resources/quality/cartesio/arnitel/cartesio_0.4_arnitel2045_normal.inst.cfg
+++ b/resources/quality/cartesio/arnitel/cartesio_0.4_arnitel2045_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = cartesio
diff --git a/resources/quality/cartesio/cartesio_global_Coarse_Quality.inst.cfg b/resources/quality/cartesio/cartesio_global_Coarse_Quality.inst.cfg
index 53cbb6bf06..017483f79a 100644
--- a/resources/quality/cartesio/cartesio_global_Coarse_Quality.inst.cfg
+++ b/resources/quality/cartesio/cartesio_global_Coarse_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Coarse
definition = cartesio
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 4d612db89f..aadb582309 100644
--- a/resources/quality/cartesio/cartesio_global_Extra_Coarse_Quality.inst.cfg
+++ b/resources/quality/cartesio/cartesio_global_Extra_Coarse_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Coarse
definition = cartesio
diff --git a/resources/quality/cartesio/cartesio_global_High_Quality.inst.cfg b/resources/quality/cartesio/cartesio_global_High_Quality.inst.cfg
index b9e4f287a1..e4131dc3a2 100644
--- a/resources/quality/cartesio/cartesio_global_High_Quality.inst.cfg
+++ b/resources/quality/cartesio/cartesio_global_High_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High
definition = cartesio
diff --git a/resources/quality/cartesio/cartesio_global_Normal_Quality.inst.cfg b/resources/quality/cartesio/cartesio_global_Normal_Quality.inst.cfg
index 1b3af7c9ce..e29a98c980 100644
--- a/resources/quality/cartesio/cartesio_global_Normal_Quality.inst.cfg
+++ b/resources/quality/cartesio/cartesio_global_Normal_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = cartesio
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 df6ca1d0a0..b2dc76479f 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High
definition = cartesio
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 663276681f..c18167b97a 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = cartesio
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 54b6edc507..506ff1807b 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High
definition = cartesio
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 c843f1653d..1f2ee24064 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = cartesio
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 d848e3ee3b..d117df88a6 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Coarse
definition = cartesio
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 7f2d436f32..e833abcdad 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Coarse
definition = cartesio
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 0e529a97e9..eee5862fea 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High
definition = cartesio
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 7c4fcbcfdd..b0057d5b03 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = cartesio
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 7690040f8c..6dce2b7ea2 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High
definition = cartesio
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 fcff2b6e4b..169a7cab65 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = cartesio
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 0cca726dcc..101fb595d5 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High
definition = cartesio
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 d231b7c296..dc5522bda3 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = cartesio
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 22eb78ec00..0b4ef88282 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Coarse
definition = cartesio
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 31f5d45a88..2fd9aa3220 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Coarse
definition = cartesio
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 9287ec9a4c..26f749e357 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High
definition = cartesio
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 ecf83c29bd..4843c9cbf4 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = cartesio
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 23df3d0451..d29c8c6801 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High
definition = cartesio
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 0ac876a538..35168f8ed7 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = cartesio
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 0c92dc32fc..05bb623a90 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High
definition = cartesio
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 2d53790fe7..569ecb069a 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = cartesio
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 5d807c5d65..b35681187d 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Coarse
definition = cartesio
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 75c72a1f56..75fe030443 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Coarse
definition = cartesio
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 0b1469fde4..05232ac1b3 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High
definition = cartesio
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 7842a91f61..106afcf992 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = cartesio
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 5688828343..edeb791847 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High
definition = cartesio
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 e47b9849ad..ca95ba4d55 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = cartesio
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 eb0cc83a63..47e4e74fba 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High
definition = cartesio
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 5b2e8bb687..737289f778 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = cartesio
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 c65551c56c..54abbbeb46 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Coarse
definition = cartesio
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 52099fc789..75ef0ed89b 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Coarse
definition = cartesio
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 d6932f9c07..2a9542ce56 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High
definition = cartesio
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 6289073085..9f27c3c8d1 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = cartesio
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 8b614f5bef..62cf3194c0 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High
definition = cartesio
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 56ea324826..5dd27477e1 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = cartesio
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 e83d26581d..b6bf3808be 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High
definition = cartesio
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 1c0b440904..a03b316e6f 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = cartesio
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 9a6a517a93..6284ab3325 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Coarse
definition = cartesio
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 17ef04e0c0..58b4c347f3 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Coarse
definition = cartesio
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 6a4b88788a..f0d22251bb 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High
definition = cartesio
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 515e473bb0..44ed46ddd3 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = cartesio
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 dfbde35b9c..aa9ac8393d 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High
definition = cartesio
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 ed1fa07207..b4de6b5730 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = cartesio
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 a2c4a21674..5a60dfbe17 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High
definition = cartesio
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 91164306fb..9cc57ac16a 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = cartesio
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 003f980901..df1bffeb41 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Coarse
definition = cartesio
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 c328442ee8..30ade55494 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Coarse
definition = cartesio
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 4830d5bcc9..c8ab571baf 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High
definition = cartesio
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 f739f1668b..815694e410 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = cartesio
diff --git a/resources/quality/coarse.inst.cfg b/resources/quality/coarse.inst.cfg
index 1accd77807..9dff2a02b3 100644
--- a/resources/quality/coarse.inst.cfg
+++ b/resources/quality/coarse.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Coarse Quality
definition = fdmprinter
diff --git a/resources/quality/deltacomb/deltacomb_abs_fast.inst.cfg b/resources/quality/deltacomb/deltacomb_abs_fast.inst.cfg
index 75774fc000..3863cb6940 100644
--- a/resources/quality/deltacomb/deltacomb_abs_fast.inst.cfg
+++ b/resources/quality/deltacomb/deltacomb_abs_fast.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
definition = deltacomb
name = Fast Quality (beta)
diff --git a/resources/quality/deltacomb/deltacomb_abs_high.inst.cfg b/resources/quality/deltacomb/deltacomb_abs_high.inst.cfg
index 65542f114a..715d8a6841 100644
--- a/resources/quality/deltacomb/deltacomb_abs_high.inst.cfg
+++ b/resources/quality/deltacomb/deltacomb_abs_high.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
definition = deltacomb
name = High Quality (beta)
diff --git a/resources/quality/deltacomb/deltacomb_abs_normal.inst.cfg b/resources/quality/deltacomb/deltacomb_abs_normal.inst.cfg
index 55248345fa..7cddbb154a 100644
--- a/resources/quality/deltacomb/deltacomb_abs_normal.inst.cfg
+++ b/resources/quality/deltacomb/deltacomb_abs_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
definition = deltacomb
name = Normal Quality (beta)
diff --git a/resources/quality/deltacomb/deltacomb_nylon_fast.inst.cfg b/resources/quality/deltacomb/deltacomb_nylon_fast.inst.cfg
index 5986c65872..72d2b30199 100644
--- a/resources/quality/deltacomb/deltacomb_nylon_fast.inst.cfg
+++ b/resources/quality/deltacomb/deltacomb_nylon_fast.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast Quality (beta)
definition = deltacomb
diff --git a/resources/quality/deltacomb/deltacomb_nylon_high.inst.cfg b/resources/quality/deltacomb/deltacomb_nylon_high.inst.cfg
index 158f903bab..2cd71a96b1 100644
--- a/resources/quality/deltacomb/deltacomb_nylon_high.inst.cfg
+++ b/resources/quality/deltacomb/deltacomb_nylon_high.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High Quality (beta)
definition = deltacomb
diff --git a/resources/quality/deltacomb/deltacomb_nylon_normal.inst.cfg b/resources/quality/deltacomb/deltacomb_nylon_normal.inst.cfg
index 89024f30b7..d42fdee730 100644
--- a/resources/quality/deltacomb/deltacomb_nylon_normal.inst.cfg
+++ b/resources/quality/deltacomb/deltacomb_nylon_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal Quality (beta)
definition = deltacomb
diff --git a/resources/quality/deltacomb/deltacomb_pla_fast.inst.cfg b/resources/quality/deltacomb/deltacomb_pla_fast.inst.cfg
index 98a5578d89..11fefbaed1 100644
--- a/resources/quality/deltacomb/deltacomb_pla_fast.inst.cfg
+++ b/resources/quality/deltacomb/deltacomb_pla_fast.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
definition = deltacomb
name = Fast Quality
diff --git a/resources/quality/deltacomb/deltacomb_pla_high.inst.cfg b/resources/quality/deltacomb/deltacomb_pla_high.inst.cfg
index 554312ce7b..4b7125dbb9 100644
--- a/resources/quality/deltacomb/deltacomb_pla_high.inst.cfg
+++ b/resources/quality/deltacomb/deltacomb_pla_high.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
definition = deltacomb
name = High Quality
diff --git a/resources/quality/deltacomb/deltacomb_pla_normal.inst.cfg b/resources/quality/deltacomb/deltacomb_pla_normal.inst.cfg
index 3f22aa1200..e935c45567 100644
--- a/resources/quality/deltacomb/deltacomb_pla_normal.inst.cfg
+++ b/resources/quality/deltacomb/deltacomb_pla_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
definition = deltacomb
name = Normal Quality
diff --git a/resources/quality/draft.inst.cfg b/resources/quality/draft.inst.cfg
index 2b375878b6..211525e7d6 100644
--- a/resources/quality/draft.inst.cfg
+++ b/resources/quality/draft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Draft Quality
definition = fdmprinter
diff --git a/resources/quality/extra_coarse.inst.cfg b/resources/quality/extra_coarse.inst.cfg
index bc8257a97f..e25b813f2f 100644
--- a/resources/quality/extra_coarse.inst.cfg
+++ b/resources/quality/extra_coarse.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Coarse Quality
definition = fdmprinter
diff --git a/resources/quality/fabtotum/fabtotum_abs_fast.inst.cfg b/resources/quality/fabtotum/fabtotum_abs_fast.inst.cfg
index 54ad42537e..78a4eb6f9f 100644
--- a/resources/quality/fabtotum/fabtotum_abs_fast.inst.cfg
+++ b/resources/quality/fabtotum/fabtotum_abs_fast.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
definition = fabtotum
name = Fast Quality
diff --git a/resources/quality/fabtotum/fabtotum_abs_high.inst.cfg b/resources/quality/fabtotum/fabtotum_abs_high.inst.cfg
index a7a4e71709..786ae18fa5 100644
--- a/resources/quality/fabtotum/fabtotum_abs_high.inst.cfg
+++ b/resources/quality/fabtotum/fabtotum_abs_high.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
definition = fabtotum
name = High Quality
diff --git a/resources/quality/fabtotum/fabtotum_abs_normal.inst.cfg b/resources/quality/fabtotum/fabtotum_abs_normal.inst.cfg
index 19d6062dfa..da75417c87 100644
--- a/resources/quality/fabtotum/fabtotum_abs_normal.inst.cfg
+++ b/resources/quality/fabtotum/fabtotum_abs_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
definition = fabtotum
name = Normal Quality
diff --git a/resources/quality/fabtotum/fabtotum_nylon_fast.inst.cfg b/resources/quality/fabtotum/fabtotum_nylon_fast.inst.cfg
index 1983191e90..db86543322 100644
--- a/resources/quality/fabtotum/fabtotum_nylon_fast.inst.cfg
+++ b/resources/quality/fabtotum/fabtotum_nylon_fast.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast Quality
definition = fabtotum
diff --git a/resources/quality/fabtotum/fabtotum_nylon_high.inst.cfg b/resources/quality/fabtotum/fabtotum_nylon_high.inst.cfg
index 5f81c12f22..010298c472 100644
--- a/resources/quality/fabtotum/fabtotum_nylon_high.inst.cfg
+++ b/resources/quality/fabtotum/fabtotum_nylon_high.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High Quality
definition = fabtotum
diff --git a/resources/quality/fabtotum/fabtotum_nylon_normal.inst.cfg b/resources/quality/fabtotum/fabtotum_nylon_normal.inst.cfg
index 834d9b8006..b9a80d82b3 100644
--- a/resources/quality/fabtotum/fabtotum_nylon_normal.inst.cfg
+++ b/resources/quality/fabtotum/fabtotum_nylon_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal Quality
definition = fabtotum
diff --git a/resources/quality/fabtotum/fabtotum_pla_fast.inst.cfg b/resources/quality/fabtotum/fabtotum_pla_fast.inst.cfg
index 2f123bc05b..bea0ea4aff 100644
--- a/resources/quality/fabtotum/fabtotum_pla_fast.inst.cfg
+++ b/resources/quality/fabtotum/fabtotum_pla_fast.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
definition = fabtotum
name = Fast Quality
diff --git a/resources/quality/fabtotum/fabtotum_pla_high.inst.cfg b/resources/quality/fabtotum/fabtotum_pla_high.inst.cfg
index 4b3aff15d9..b77a0d8300 100644
--- a/resources/quality/fabtotum/fabtotum_pla_high.inst.cfg
+++ b/resources/quality/fabtotum/fabtotum_pla_high.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
definition = fabtotum
name = High Quality
diff --git a/resources/quality/fabtotum/fabtotum_pla_normal.inst.cfg b/resources/quality/fabtotum/fabtotum_pla_normal.inst.cfg
index 44ddbcb085..11e5890cc5 100644
--- a/resources/quality/fabtotum/fabtotum_pla_normal.inst.cfg
+++ b/resources/quality/fabtotum/fabtotum_pla_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
definition = fabtotum
name = Normal Quality
diff --git a/resources/quality/fabtotum/fabtotum_tpu_fast.inst.cfg b/resources/quality/fabtotum/fabtotum_tpu_fast.inst.cfg
index d8ee095d61..d689f704aa 100644
--- a/resources/quality/fabtotum/fabtotum_tpu_fast.inst.cfg
+++ b/resources/quality/fabtotum/fabtotum_tpu_fast.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
definition = fabtotum
name = Fast Quality
diff --git a/resources/quality/fabtotum/fabtotum_tpu_high.inst.cfg b/resources/quality/fabtotum/fabtotum_tpu_high.inst.cfg
index 315a56015d..6193b3b573 100644
--- a/resources/quality/fabtotum/fabtotum_tpu_high.inst.cfg
+++ b/resources/quality/fabtotum/fabtotum_tpu_high.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
definition = fabtotum
name = High Quality
diff --git a/resources/quality/fabtotum/fabtotum_tpu_normal.inst.cfg b/resources/quality/fabtotum/fabtotum_tpu_normal.inst.cfg
index 3ce1592e70..7ccbe296e3 100644
--- a/resources/quality/fabtotum/fabtotum_tpu_normal.inst.cfg
+++ b/resources/quality/fabtotum/fabtotum_tpu_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
definition = fabtotum
name = Normal Quality
diff --git a/resources/quality/fast.inst.cfg b/resources/quality/fast.inst.cfg
index 4b78cfcd75..56bc6be48d 100644
--- a/resources/quality/fast.inst.cfg
+++ b/resources/quality/fast.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Low Quality
definition = fdmprinter
diff --git a/resources/quality/gmax15plus/gmax15plus_pla_dual_normal.inst.cfg b/resources/quality/gmax15plus/gmax15plus_pla_dual_normal.inst.cfg
index e08a6ff421..ae31f92447 100644
--- a/resources/quality/gmax15plus/gmax15plus_pla_dual_normal.inst.cfg
+++ b/resources/quality/gmax15plus/gmax15plus_pla_dual_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = gMax 1.5+ Dual Normal Layers
definition = gmax15plus_dual
diff --git a/resources/quality/gmax15plus/gmax15plus_pla_dual_thick.inst.cfg b/resources/quality/gmax15plus/gmax15plus_pla_dual_thick.inst.cfg
index 86bfe2af6c..4e262d8ecf 100644
--- a/resources/quality/gmax15plus/gmax15plus_pla_dual_thick.inst.cfg
+++ b/resources/quality/gmax15plus/gmax15plus_pla_dual_thick.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = gMax 1.5+ Dual Thick Layers
definition = gmax15plus_dual
diff --git a/resources/quality/gmax15plus/gmax15plus_pla_dual_thin.inst.cfg b/resources/quality/gmax15plus/gmax15plus_pla_dual_thin.inst.cfg
index 0d19c6c9f0..a1b43d5d08 100644
--- a/resources/quality/gmax15plus/gmax15plus_pla_dual_thin.inst.cfg
+++ b/resources/quality/gmax15plus/gmax15plus_pla_dual_thin.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = gMax 1.5+ Dual Thin Layers
definition = gmax15plus_dual
diff --git a/resources/quality/gmax15plus/gmax15plus_pla_dual_very_thick.inst.cfg b/resources/quality/gmax15plus/gmax15plus_pla_dual_very_thick.inst.cfg
index 2b58120762..1d5c7eca91 100644
--- a/resources/quality/gmax15plus/gmax15plus_pla_dual_very_thick.inst.cfg
+++ b/resources/quality/gmax15plus/gmax15plus_pla_dual_very_thick.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = gMax 1.5+ Dual Very Thick Layers
definition = gmax15plus_dual
diff --git a/resources/quality/gmax15plus/gmax15plus_pla_normal.inst.cfg b/resources/quality/gmax15plus/gmax15plus_pla_normal.inst.cfg
index 70920b6f6a..1d01b82d3c 100644
--- a/resources/quality/gmax15plus/gmax15plus_pla_normal.inst.cfg
+++ b/resources/quality/gmax15plus/gmax15plus_pla_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = gMax 1.5+ Normal Layers
definition = gmax15plus
diff --git a/resources/quality/gmax15plus/gmax15plus_pla_thick.inst.cfg b/resources/quality/gmax15plus/gmax15plus_pla_thick.inst.cfg
index 0f1e8a3802..dd6b3e702b 100644
--- a/resources/quality/gmax15plus/gmax15plus_pla_thick.inst.cfg
+++ b/resources/quality/gmax15plus/gmax15plus_pla_thick.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = gMax 1.5+ Thick Layers
definition = gmax15plus
diff --git a/resources/quality/gmax15plus/gmax15plus_pla_thin.inst.cfg b/resources/quality/gmax15plus/gmax15plus_pla_thin.inst.cfg
index f548affc2c..f90cb27647 100644
--- a/resources/quality/gmax15plus/gmax15plus_pla_thin.inst.cfg
+++ b/resources/quality/gmax15plus/gmax15plus_pla_thin.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = gMax 1.5+ Thin Layers
definition = gmax15plus
diff --git a/resources/quality/gmax15plus/gmax15plus_pla_very_thick.inst.cfg b/resources/quality/gmax15plus/gmax15plus_pla_very_thick.inst.cfg
index 5db77a70ea..171cf2f28d 100644
--- a/resources/quality/gmax15plus/gmax15plus_pla_very_thick.inst.cfg
+++ b/resources/quality/gmax15plus/gmax15plus_pla_very_thick.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = gMax 1.5+ Very Thick Layers
definition = gmax15plus
diff --git a/resources/quality/high.inst.cfg b/resources/quality/high.inst.cfg
index 9ef90181a3..bb9e77ad38 100644
--- a/resources/quality/high.inst.cfg
+++ b/resources/quality/high.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fine
definition = fdmprinter
diff --git a/resources/quality/imade3d_jellybox/generic_petg_0.4_coarse.inst.cfg b/resources/quality/imade3d_jellybox/generic_petg_0.4_coarse.inst.cfg
index 4d0493ae8b..020e9d9b0f 100644
--- a/resources/quality/imade3d_jellybox/generic_petg_0.4_coarse.inst.cfg
+++ b/resources/quality/imade3d_jellybox/generic_petg_0.4_coarse.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Coarse
definition = imade3d_jellybox
diff --git a/resources/quality/imade3d_jellybox/generic_petg_0.4_coarse_2-fans.inst.cfg b/resources/quality/imade3d_jellybox/generic_petg_0.4_coarse_2-fans.inst.cfg
index 9251ae43b4..3aba34126e 100644
--- a/resources/quality/imade3d_jellybox/generic_petg_0.4_coarse_2-fans.inst.cfg
+++ b/resources/quality/imade3d_jellybox/generic_petg_0.4_coarse_2-fans.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Coarse
definition = imade3d_jellybox
diff --git a/resources/quality/imade3d_jellybox/generic_petg_0.4_medium.inst.cfg b/resources/quality/imade3d_jellybox/generic_petg_0.4_medium.inst.cfg
index a10a3bcf0a..b235662e9f 100644
--- a/resources/quality/imade3d_jellybox/generic_petg_0.4_medium.inst.cfg
+++ b/resources/quality/imade3d_jellybox/generic_petg_0.4_medium.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Medium
definition = imade3d_jellybox
diff --git a/resources/quality/imade3d_jellybox/generic_petg_0.4_medium_2-fans.inst.cfg b/resources/quality/imade3d_jellybox/generic_petg_0.4_medium_2-fans.inst.cfg
index b851c2e17c..d5a9b09ed7 100644
--- a/resources/quality/imade3d_jellybox/generic_petg_0.4_medium_2-fans.inst.cfg
+++ b/resources/quality/imade3d_jellybox/generic_petg_0.4_medium_2-fans.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Medium
definition = imade3d_jellybox
diff --git a/resources/quality/imade3d_jellybox/generic_pla_0.4_coarse.inst.cfg b/resources/quality/imade3d_jellybox/generic_pla_0.4_coarse.inst.cfg
index bc86119eb8..797f77fa72 100644
--- a/resources/quality/imade3d_jellybox/generic_pla_0.4_coarse.inst.cfg
+++ b/resources/quality/imade3d_jellybox/generic_pla_0.4_coarse.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Coarse
definition = imade3d_jellybox
diff --git a/resources/quality/imade3d_jellybox/generic_pla_0.4_coarse_2-fans.inst.cfg b/resources/quality/imade3d_jellybox/generic_pla_0.4_coarse_2-fans.inst.cfg
index c05927fad5..61bb573a25 100644
--- a/resources/quality/imade3d_jellybox/generic_pla_0.4_coarse_2-fans.inst.cfg
+++ b/resources/quality/imade3d_jellybox/generic_pla_0.4_coarse_2-fans.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Coarse
definition = imade3d_jellybox
diff --git a/resources/quality/imade3d_jellybox/generic_pla_0.4_fine.inst.cfg b/resources/quality/imade3d_jellybox/generic_pla_0.4_fine.inst.cfg
index 1bec96f05c..3c37910112 100644
--- a/resources/quality/imade3d_jellybox/generic_pla_0.4_fine.inst.cfg
+++ b/resources/quality/imade3d_jellybox/generic_pla_0.4_fine.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = imade3d_jellybox
diff --git a/resources/quality/imade3d_jellybox/generic_pla_0.4_fine_2-fans.inst.cfg b/resources/quality/imade3d_jellybox/generic_pla_0.4_fine_2-fans.inst.cfg
index 609662a149..eb31b07794 100644
--- a/resources/quality/imade3d_jellybox/generic_pla_0.4_fine_2-fans.inst.cfg
+++ b/resources/quality/imade3d_jellybox/generic_pla_0.4_fine_2-fans.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = imade3d_jellybox
diff --git a/resources/quality/imade3d_jellybox/generic_pla_0.4_medium.inst.cfg b/resources/quality/imade3d_jellybox/generic_pla_0.4_medium.inst.cfg
index 5249f2dc2b..b8bbd674e0 100644
--- a/resources/quality/imade3d_jellybox/generic_pla_0.4_medium.inst.cfg
+++ b/resources/quality/imade3d_jellybox/generic_pla_0.4_medium.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Medium
definition = imade3d_jellybox
diff --git a/resources/quality/imade3d_jellybox/generic_pla_0.4_medium_2-fans.inst.cfg b/resources/quality/imade3d_jellybox/generic_pla_0.4_medium_2-fans.inst.cfg
index 1534d3a6fb..56ae48379f 100644
--- a/resources/quality/imade3d_jellybox/generic_pla_0.4_medium_2-fans.inst.cfg
+++ b/resources/quality/imade3d_jellybox/generic_pla_0.4_medium_2-fans.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Medium
definition = imade3d_jellybox
diff --git a/resources/quality/imade3d_jellybox/generic_pla_0.4_ultrafine.inst.cfg b/resources/quality/imade3d_jellybox/generic_pla_0.4_ultrafine.inst.cfg
index 1166bd70c9..16fb70252b 100644
--- a/resources/quality/imade3d_jellybox/generic_pla_0.4_ultrafine.inst.cfg
+++ b/resources/quality/imade3d_jellybox/generic_pla_0.4_ultrafine.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = UltraFine
definition = imade3d_jellybox
diff --git a/resources/quality/imade3d_jellybox/generic_pla_0.4_ultrafine_2-fans.inst.cfg b/resources/quality/imade3d_jellybox/generic_pla_0.4_ultrafine_2-fans.inst.cfg
index 5b05c10604..2cab1fad46 100644
--- a/resources/quality/imade3d_jellybox/generic_pla_0.4_ultrafine_2-fans.inst.cfg
+++ b/resources/quality/imade3d_jellybox/generic_pla_0.4_ultrafine_2-fans.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = UltraFine
definition = imade3d_jellybox
diff --git a/resources/quality/imade3d_jellybox/imade3d_jellybox_coarse.inst.cfg b/resources/quality/imade3d_jellybox/imade3d_jellybox_coarse.inst.cfg
index 2e940ea646..7a778a788f 100644
--- a/resources/quality/imade3d_jellybox/imade3d_jellybox_coarse.inst.cfg
+++ b/resources/quality/imade3d_jellybox/imade3d_jellybox_coarse.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Coarse
definition = imade3d_jellybox
diff --git a/resources/quality/imade3d_jellybox/imade3d_jellybox_fine.inst.cfg b/resources/quality/imade3d_jellybox/imade3d_jellybox_fine.inst.cfg
index 9979558963..51767e0c93 100644
--- a/resources/quality/imade3d_jellybox/imade3d_jellybox_fine.inst.cfg
+++ b/resources/quality/imade3d_jellybox/imade3d_jellybox_fine.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = imade3d_jellybox
diff --git a/resources/quality/imade3d_jellybox/imade3d_jellybox_normal.inst.cfg b/resources/quality/imade3d_jellybox/imade3d_jellybox_normal.inst.cfg
index 0574099b63..407ae608a4 100644
--- a/resources/quality/imade3d_jellybox/imade3d_jellybox_normal.inst.cfg
+++ b/resources/quality/imade3d_jellybox/imade3d_jellybox_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Medium
definition = imade3d_jellybox
diff --git a/resources/quality/imade3d_jellybox/imade3d_jellybox_ultrafine.inst.cfg b/resources/quality/imade3d_jellybox/imade3d_jellybox_ultrafine.inst.cfg
index 39344ce7f4..f531d84234 100644
--- a/resources/quality/imade3d_jellybox/imade3d_jellybox_ultrafine.inst.cfg
+++ b/resources/quality/imade3d_jellybox/imade3d_jellybox_ultrafine.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = UltraFine
definition = imade3d_jellybox
diff --git a/resources/quality/kemiq_q2/kemiq_q2_beta_abs_draft.inst.cfg b/resources/quality/kemiq_q2/kemiq_q2_beta_abs_draft.inst.cfg
index add07c797a..69c2b328cf 100644
--- a/resources/quality/kemiq_q2/kemiq_q2_beta_abs_draft.inst.cfg
+++ b/resources/quality/kemiq_q2/kemiq_q2_beta_abs_draft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Draft
definition = kemiq_q2_beta
diff --git a/resources/quality/kemiq_q2/kemiq_q2_beta_abs_extra_fine.inst.cfg b/resources/quality/kemiq_q2/kemiq_q2_beta_abs_extra_fine.inst.cfg
index 8d31e439e1..0bb72ce073 100644
--- a/resources/quality/kemiq_q2/kemiq_q2_beta_abs_extra_fine.inst.cfg
+++ b/resources/quality/kemiq_q2/kemiq_q2_beta_abs_extra_fine.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fine
definition = kemiq_q2_beta
diff --git a/resources/quality/kemiq_q2/kemiq_q2_beta_abs_fine.inst.cfg b/resources/quality/kemiq_q2/kemiq_q2_beta_abs_fine.inst.cfg
index 936e9f8cd8..1fd5874b70 100644
--- a/resources/quality/kemiq_q2/kemiq_q2_beta_abs_fine.inst.cfg
+++ b/resources/quality/kemiq_q2/kemiq_q2_beta_abs_fine.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = kemiq_q2_beta
diff --git a/resources/quality/kemiq_q2/kemiq_q2_beta_abs_low.inst.cfg b/resources/quality/kemiq_q2/kemiq_q2_beta_abs_low.inst.cfg
index 8f48616dbf..78f80c576d 100644
--- a/resources/quality/kemiq_q2/kemiq_q2_beta_abs_low.inst.cfg
+++ b/resources/quality/kemiq_q2/kemiq_q2_beta_abs_low.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Low
definition = kemiq_q2_beta
diff --git a/resources/quality/kemiq_q2/kemiq_q2_beta_abs_normal.inst.cfg b/resources/quality/kemiq_q2/kemiq_q2_beta_abs_normal.inst.cfg
index 947fd0774d..626de7bde6 100644
--- a/resources/quality/kemiq_q2/kemiq_q2_beta_abs_normal.inst.cfg
+++ b/resources/quality/kemiq_q2/kemiq_q2_beta_abs_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = kemiq_q2_beta
diff --git a/resources/quality/kemiq_q2/kemiq_q2_beta_pla_draft.inst.cfg b/resources/quality/kemiq_q2/kemiq_q2_beta_pla_draft.inst.cfg
index d9ea0e6850..63a49e6bc1 100644
--- a/resources/quality/kemiq_q2/kemiq_q2_beta_pla_draft.inst.cfg
+++ b/resources/quality/kemiq_q2/kemiq_q2_beta_pla_draft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Draft
definition = kemiq_q2_beta
diff --git a/resources/quality/kemiq_q2/kemiq_q2_beta_pla_extra_fine.inst.cfg b/resources/quality/kemiq_q2/kemiq_q2_beta_pla_extra_fine.inst.cfg
index b1775b994f..2d49b01b92 100644
--- a/resources/quality/kemiq_q2/kemiq_q2_beta_pla_extra_fine.inst.cfg
+++ b/resources/quality/kemiq_q2/kemiq_q2_beta_pla_extra_fine.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fine
definition = kemiq_q2_beta
diff --git a/resources/quality/kemiq_q2/kemiq_q2_beta_pla_fine.inst.cfg b/resources/quality/kemiq_q2/kemiq_q2_beta_pla_fine.inst.cfg
index cf7e070cb8..e79a3188d7 100644
--- a/resources/quality/kemiq_q2/kemiq_q2_beta_pla_fine.inst.cfg
+++ b/resources/quality/kemiq_q2/kemiq_q2_beta_pla_fine.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = kemiq_q2_beta
diff --git a/resources/quality/kemiq_q2/kemiq_q2_beta_pla_low.inst.cfg b/resources/quality/kemiq_q2/kemiq_q2_beta_pla_low.inst.cfg
index 8b40fabb7b..db391b95ca 100644
--- a/resources/quality/kemiq_q2/kemiq_q2_beta_pla_low.inst.cfg
+++ b/resources/quality/kemiq_q2/kemiq_q2_beta_pla_low.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Low
definition = kemiq_q2_beta
diff --git a/resources/quality/kemiq_q2/kemiq_q2_beta_pla_normal.inst.cfg b/resources/quality/kemiq_q2/kemiq_q2_beta_pla_normal.inst.cfg
index cc7d877c70..336b1f47b9 100644
--- a/resources/quality/kemiq_q2/kemiq_q2_beta_pla_normal.inst.cfg
+++ b/resources/quality/kemiq_q2/kemiq_q2_beta_pla_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = kemiq_q2_beta
diff --git a/resources/quality/kemiq_q2/kemiq_q2_gama_pla_draft.inst.cfg b/resources/quality/kemiq_q2/kemiq_q2_gama_pla_draft.inst.cfg
index fcda1b081e..6044fa25a4 100644
--- a/resources/quality/kemiq_q2/kemiq_q2_gama_pla_draft.inst.cfg
+++ b/resources/quality/kemiq_q2/kemiq_q2_gama_pla_draft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Draft
definition = kemiq_q2_gama
diff --git a/resources/quality/kemiq_q2/kemiq_q2_gama_pla_extra_fine.inst.cfg b/resources/quality/kemiq_q2/kemiq_q2_gama_pla_extra_fine.inst.cfg
index 52e5e8df98..9962c554ca 100644
--- a/resources/quality/kemiq_q2/kemiq_q2_gama_pla_extra_fine.inst.cfg
+++ b/resources/quality/kemiq_q2/kemiq_q2_gama_pla_extra_fine.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fine
definition = kemiq_q2_gama
diff --git a/resources/quality/kemiq_q2/kemiq_q2_gama_pla_fine.inst.cfg b/resources/quality/kemiq_q2/kemiq_q2_gama_pla_fine.inst.cfg
index b3ea5b8967..4b5b3b62ed 100644
--- a/resources/quality/kemiq_q2/kemiq_q2_gama_pla_fine.inst.cfg
+++ b/resources/quality/kemiq_q2/kemiq_q2_gama_pla_fine.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = kemiq_q2_gama
diff --git a/resources/quality/kemiq_q2/kemiq_q2_gama_pla_low.inst.cfg b/resources/quality/kemiq_q2/kemiq_q2_gama_pla_low.inst.cfg
index 08a3db0b1f..a187507632 100644
--- a/resources/quality/kemiq_q2/kemiq_q2_gama_pla_low.inst.cfg
+++ b/resources/quality/kemiq_q2/kemiq_q2_gama_pla_low.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Low
definition = kemiq_q2_gama
diff --git a/resources/quality/kemiq_q2/kemiq_q2_gama_pla_normal.inst.cfg b/resources/quality/kemiq_q2/kemiq_q2_gama_pla_normal.inst.cfg
index a71c3c731b..281f73393a 100644
--- a/resources/quality/kemiq_q2/kemiq_q2_gama_pla_normal.inst.cfg
+++ b/resources/quality/kemiq_q2/kemiq_q2_gama_pla_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = kemiq_q2_gama
diff --git a/resources/quality/malyan_m200/abs/malyan_m200_abs_draft.inst.cfg b/resources/quality/malyan_m200/abs/malyan_m200_abs_draft.inst.cfg
index c02e789dfe..104b747aea 100644
--- a/resources/quality/malyan_m200/abs/malyan_m200_abs_draft.inst.cfg
+++ b/resources/quality/malyan_m200/abs/malyan_m200_abs_draft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/abs/malyan_m200_abs_fast.inst.cfg b/resources/quality/malyan_m200/abs/malyan_m200_abs_fast.inst.cfg
index 7f4f368e06..3f2eec9867 100644
--- a/resources/quality/malyan_m200/abs/malyan_m200_abs_fast.inst.cfg
+++ b/resources/quality/malyan_m200/abs/malyan_m200_abs_fast.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/abs/malyan_m200_abs_high.inst.cfg b/resources/quality/malyan_m200/abs/malyan_m200_abs_high.inst.cfg
index 37335b61ee..55a6c0d4ba 100644
--- a/resources/quality/malyan_m200/abs/malyan_m200_abs_high.inst.cfg
+++ b/resources/quality/malyan_m200/abs/malyan_m200_abs_high.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Finer
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/abs/malyan_m200_abs_normal.inst.cfg b/resources/quality/malyan_m200/abs/malyan_m200_abs_normal.inst.cfg
index 138c31ab14..98587bdf7d 100644
--- a/resources/quality/malyan_m200/abs/malyan_m200_abs_normal.inst.cfg
+++ b/resources/quality/malyan_m200/abs/malyan_m200_abs_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/abs/malyan_m200_abs_superdraft.inst.cfg b/resources/quality/malyan_m200/abs/malyan_m200_abs_superdraft.inst.cfg
index 2d748bd698..49fcd1e935 100644
--- a/resources/quality/malyan_m200/abs/malyan_m200_abs_superdraft.inst.cfg
+++ b/resources/quality/malyan_m200/abs/malyan_m200_abs_superdraft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Lowest Quality Draft
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/abs/malyan_m200_abs_thickerdraft.inst.cfg b/resources/quality/malyan_m200/abs/malyan_m200_abs_thickerdraft.inst.cfg
index ebf7798733..f9a9fe3d98 100644
--- a/resources/quality/malyan_m200/abs/malyan_m200_abs_thickerdraft.inst.cfg
+++ b/resources/quality/malyan_m200/abs/malyan_m200_abs_thickerdraft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Draft
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/abs/malyan_m200_abs_ultra.inst.cfg b/resources/quality/malyan_m200/abs/malyan_m200_abs_ultra.inst.cfg
index 5be92914be..dcc2813031 100644
--- a/resources/quality/malyan_m200/abs/malyan_m200_abs_ultra.inst.cfg
+++ b/resources/quality/malyan_m200/abs/malyan_m200_abs_ultra.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Ultra Fine
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/abs/malyan_m200_abs_verydraft.inst.cfg b/resources/quality/malyan_m200/abs/malyan_m200_abs_verydraft.inst.cfg
index bfe92a98e1..251d024a85 100644
--- a/resources/quality/malyan_m200/abs/malyan_m200_abs_verydraft.inst.cfg
+++ b/resources/quality/malyan_m200/abs/malyan_m200_abs_verydraft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Low Detail Draft
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/malyan_m200_0.04375.inst.cfg b/resources/quality/malyan_m200/malyan_m200_0.04375.inst.cfg
index 89d7f2f2ec..c9dd72f5f5 100644
--- a/resources/quality/malyan_m200/malyan_m200_0.04375.inst.cfg
+++ b/resources/quality/malyan_m200/malyan_m200_0.04375.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = M1 Quality
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/malyan_m200_0.0875.inst.cfg b/resources/quality/malyan_m200/malyan_m200_0.0875.inst.cfg
index 555b280c1c..65d7d0d0b8 100644
--- a/resources/quality/malyan_m200/malyan_m200_0.0875.inst.cfg
+++ b/resources/quality/malyan_m200/malyan_m200_0.0875.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = M2 Quality
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/malyan_m200_0.13125.inst.cfg b/resources/quality/malyan_m200/malyan_m200_0.13125.inst.cfg
index 7829f33086..89aea54fdc 100644
--- a/resources/quality/malyan_m200/malyan_m200_0.13125.inst.cfg
+++ b/resources/quality/malyan_m200/malyan_m200_0.13125.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = M3 Quality
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/malyan_m200_0.175.inst.cfg b/resources/quality/malyan_m200/malyan_m200_0.175.inst.cfg
index e6fd4c9368..613988a437 100644
--- a/resources/quality/malyan_m200/malyan_m200_0.175.inst.cfg
+++ b/resources/quality/malyan_m200/malyan_m200_0.175.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = M4 Quality
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/malyan_m200_0.21875.inst.cfg b/resources/quality/malyan_m200/malyan_m200_0.21875.inst.cfg
index 143693dbc4..75d7601af3 100644
--- a/resources/quality/malyan_m200/malyan_m200_0.21875.inst.cfg
+++ b/resources/quality/malyan_m200/malyan_m200_0.21875.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = M5 Quality
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/malyan_m200_0.2625.inst.cfg b/resources/quality/malyan_m200/malyan_m200_0.2625.inst.cfg
index 3dc33a81a0..470af78595 100644
--- a/resources/quality/malyan_m200/malyan_m200_0.2625.inst.cfg
+++ b/resources/quality/malyan_m200/malyan_m200_0.2625.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = M6 Quality
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/malyan_m200_0.30625.inst.cfg b/resources/quality/malyan_m200/malyan_m200_0.30625.inst.cfg
index c7d6911a67..32fa8c5641 100644
--- a/resources/quality/malyan_m200/malyan_m200_0.30625.inst.cfg
+++ b/resources/quality/malyan_m200/malyan_m200_0.30625.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = M7 Quality
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/malyan_m200_0.35.inst.cfg b/resources/quality/malyan_m200/malyan_m200_0.35.inst.cfg
index 5d14204a1e..1e168e7690 100644
--- a/resources/quality/malyan_m200/malyan_m200_0.35.inst.cfg
+++ b/resources/quality/malyan_m200/malyan_m200_0.35.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = M8 Quality
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/malyan_m200_global_Draft_Quality.inst.cfg b/resources/quality/malyan_m200/malyan_m200_global_Draft_Quality.inst.cfg
index fd999cc6c7..12796a140f 100644
--- a/resources/quality/malyan_m200/malyan_m200_global_Draft_Quality.inst.cfg
+++ b/resources/quality/malyan_m200/malyan_m200_global_Draft_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/malyan_m200_global_Fast_Quality.inst.cfg b/resources/quality/malyan_m200/malyan_m200_global_Fast_Quality.inst.cfg
index 8027a7b01a..78f014bd9e 100644
--- a/resources/quality/malyan_m200/malyan_m200_global_Fast_Quality.inst.cfg
+++ b/resources/quality/malyan_m200/malyan_m200_global_Fast_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/malyan_m200_global_High_Quality.inst.cfg b/resources/quality/malyan_m200/malyan_m200_global_High_Quality.inst.cfg
index 01fea67382..bf2f25b418 100644
--- a/resources/quality/malyan_m200/malyan_m200_global_High_Quality.inst.cfg
+++ b/resources/quality/malyan_m200/malyan_m200_global_High_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Finer
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/malyan_m200_global_Normal_Quality.inst.cfg b/resources/quality/malyan_m200/malyan_m200_global_Normal_Quality.inst.cfg
index a705d187f1..cd9609cc2d 100644
--- a/resources/quality/malyan_m200/malyan_m200_global_Normal_Quality.inst.cfg
+++ b/resources/quality/malyan_m200/malyan_m200_global_Normal_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/malyan_m200_global_SuperDraft_Quality.inst.cfg b/resources/quality/malyan_m200/malyan_m200_global_SuperDraft_Quality.inst.cfg
index 92f1eb2286..880eb5068d 100644
--- a/resources/quality/malyan_m200/malyan_m200_global_SuperDraft_Quality.inst.cfg
+++ b/resources/quality/malyan_m200/malyan_m200_global_SuperDraft_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Lowest Quality Draft
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/malyan_m200_global_ThickerDraft_Quality.inst.cfg b/resources/quality/malyan_m200/malyan_m200_global_ThickerDraft_Quality.inst.cfg
index 246443503e..9ebc812738 100644
--- a/resources/quality/malyan_m200/malyan_m200_global_ThickerDraft_Quality.inst.cfg
+++ b/resources/quality/malyan_m200/malyan_m200_global_ThickerDraft_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Draft
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/malyan_m200_global_Ultra_Quality.inst.cfg b/resources/quality/malyan_m200/malyan_m200_global_Ultra_Quality.inst.cfg
index 0bbe46be4e..f1841970af 100644
--- a/resources/quality/malyan_m200/malyan_m200_global_Ultra_Quality.inst.cfg
+++ b/resources/quality/malyan_m200/malyan_m200_global_Ultra_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Ultra Fine
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/malyan_m200_global_VeryDraft_Quality.inst.cfg b/resources/quality/malyan_m200/malyan_m200_global_VeryDraft_Quality.inst.cfg
index 5cc85b9bc8..7da342c437 100644
--- a/resources/quality/malyan_m200/malyan_m200_global_VeryDraft_Quality.inst.cfg
+++ b/resources/quality/malyan_m200/malyan_m200_global_VeryDraft_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Low Detail Draft
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/petg/malyan_m200_petg_draft.inst.cfg b/resources/quality/malyan_m200/petg/malyan_m200_petg_draft.inst.cfg
index 038af5eec0..0434decedc 100644
--- a/resources/quality/malyan_m200/petg/malyan_m200_petg_draft.inst.cfg
+++ b/resources/quality/malyan_m200/petg/malyan_m200_petg_draft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/petg/malyan_m200_petg_fast.inst.cfg b/resources/quality/malyan_m200/petg/malyan_m200_petg_fast.inst.cfg
index df341d195c..2049403af4 100644
--- a/resources/quality/malyan_m200/petg/malyan_m200_petg_fast.inst.cfg
+++ b/resources/quality/malyan_m200/petg/malyan_m200_petg_fast.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/petg/malyan_m200_petg_high.inst.cfg b/resources/quality/malyan_m200/petg/malyan_m200_petg_high.inst.cfg
index ff9dbc8227..00882f2418 100644
--- a/resources/quality/malyan_m200/petg/malyan_m200_petg_high.inst.cfg
+++ b/resources/quality/malyan_m200/petg/malyan_m200_petg_high.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Finer
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/petg/malyan_m200_petg_normal.inst.cfg b/resources/quality/malyan_m200/petg/malyan_m200_petg_normal.inst.cfg
index 8531bbf98b..58ad4a1085 100644
--- a/resources/quality/malyan_m200/petg/malyan_m200_petg_normal.inst.cfg
+++ b/resources/quality/malyan_m200/petg/malyan_m200_petg_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/petg/malyan_m200_petg_superdraft.inst.cfg b/resources/quality/malyan_m200/petg/malyan_m200_petg_superdraft.inst.cfg
index 8b065ecea8..09a72e261a 100644
--- a/resources/quality/malyan_m200/petg/malyan_m200_petg_superdraft.inst.cfg
+++ b/resources/quality/malyan_m200/petg/malyan_m200_petg_superdraft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Lowest Quality Draft
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/petg/malyan_m200_petg_thickerdraft.inst.cfg b/resources/quality/malyan_m200/petg/malyan_m200_petg_thickerdraft.inst.cfg
index 97672ee1c7..e57b1d24f2 100644
--- a/resources/quality/malyan_m200/petg/malyan_m200_petg_thickerdraft.inst.cfg
+++ b/resources/quality/malyan_m200/petg/malyan_m200_petg_thickerdraft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Draft
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/petg/malyan_m200_petg_ultra.inst.cfg b/resources/quality/malyan_m200/petg/malyan_m200_petg_ultra.inst.cfg
index 7b92f9d454..0c3de72835 100644
--- a/resources/quality/malyan_m200/petg/malyan_m200_petg_ultra.inst.cfg
+++ b/resources/quality/malyan_m200/petg/malyan_m200_petg_ultra.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Ultra Fine
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/petg/malyan_m200_petg_verydraft.inst.cfg b/resources/quality/malyan_m200/petg/malyan_m200_petg_verydraft.inst.cfg
index 1a1b59d90f..0139e972f6 100644
--- a/resources/quality/malyan_m200/petg/malyan_m200_petg_verydraft.inst.cfg
+++ b/resources/quality/malyan_m200/petg/malyan_m200_petg_verydraft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Low Detail Draft
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/pla/malyan_m200_pla_draft.inst.cfg b/resources/quality/malyan_m200/pla/malyan_m200_pla_draft.inst.cfg
index f3ef39f009..c30df8a9b3 100644
--- a/resources/quality/malyan_m200/pla/malyan_m200_pla_draft.inst.cfg
+++ b/resources/quality/malyan_m200/pla/malyan_m200_pla_draft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/pla/malyan_m200_pla_fast.inst.cfg b/resources/quality/malyan_m200/pla/malyan_m200_pla_fast.inst.cfg
index 0e047b140e..8e1b0478ad 100644
--- a/resources/quality/malyan_m200/pla/malyan_m200_pla_fast.inst.cfg
+++ b/resources/quality/malyan_m200/pla/malyan_m200_pla_fast.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/pla/malyan_m200_pla_high.inst.cfg b/resources/quality/malyan_m200/pla/malyan_m200_pla_high.inst.cfg
index e68ad90c6c..44713b2386 100644
--- a/resources/quality/malyan_m200/pla/malyan_m200_pla_high.inst.cfg
+++ b/resources/quality/malyan_m200/pla/malyan_m200_pla_high.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Finer
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/pla/malyan_m200_pla_normal.inst.cfg b/resources/quality/malyan_m200/pla/malyan_m200_pla_normal.inst.cfg
index 78bdc826ba..cd6497d9bf 100644
--- a/resources/quality/malyan_m200/pla/malyan_m200_pla_normal.inst.cfg
+++ b/resources/quality/malyan_m200/pla/malyan_m200_pla_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/pla/malyan_m200_pla_superdraft.inst.cfg b/resources/quality/malyan_m200/pla/malyan_m200_pla_superdraft.inst.cfg
index cc24c4a0b2..447de8a48b 100644
--- a/resources/quality/malyan_m200/pla/malyan_m200_pla_superdraft.inst.cfg
+++ b/resources/quality/malyan_m200/pla/malyan_m200_pla_superdraft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Lowest Quality Draft
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/pla/malyan_m200_pla_thickerdraft.inst.cfg b/resources/quality/malyan_m200/pla/malyan_m200_pla_thickerdraft.inst.cfg
index 5bfb82ee90..f2c5e3c9cc 100644
--- a/resources/quality/malyan_m200/pla/malyan_m200_pla_thickerdraft.inst.cfg
+++ b/resources/quality/malyan_m200/pla/malyan_m200_pla_thickerdraft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Draft
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/pla/malyan_m200_pla_ultra.inst.cfg b/resources/quality/malyan_m200/pla/malyan_m200_pla_ultra.inst.cfg
index d042fc7978..63f19bef41 100644
--- a/resources/quality/malyan_m200/pla/malyan_m200_pla_ultra.inst.cfg
+++ b/resources/quality/malyan_m200/pla/malyan_m200_pla_ultra.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Ultra Fine
definition = malyan_m200
diff --git a/resources/quality/malyan_m200/pla/malyan_m200_pla_verydraft.inst.cfg b/resources/quality/malyan_m200/pla/malyan_m200_pla_verydraft.inst.cfg
index 8e79071db4..6a96b3d678 100644
--- a/resources/quality/malyan_m200/pla/malyan_m200_pla_verydraft.inst.cfg
+++ b/resources/quality/malyan_m200/pla/malyan_m200_pla_verydraft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Low Detail Draft
definition = malyan_m200
diff --git a/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_draft.inst.cfg b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_draft.inst.cfg
index 3387b51d7d..6fec85b563 100644
--- a/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_draft.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_draft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_fast.inst.cfg b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_fast.inst.cfg
index da187c85ea..0d79f2cc72 100644
--- a/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_fast.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_fast.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_high.inst.cfg b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_high.inst.cfg
index a2efa54353..f02e3b5838 100644
--- a/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_high.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_high.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Finer
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_normal.inst.cfg b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_normal.inst.cfg
index a331a63aaf..a046cfa561 100644
--- a/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_normal.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_superdraft.inst.cfg b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_superdraft.inst.cfg
index d328e353ac..f5861ce734 100644
--- a/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_superdraft.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_superdraft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Lowest Quality Draft
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_thickerdraft.inst.cfg b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_thickerdraft.inst.cfg
index 8e64dae5aa..b63fd3cfad 100644
--- a/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_thickerdraft.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_thickerdraft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Draft
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_ultra.inst.cfg b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_ultra.inst.cfg
index 5757e57a3b..6aed450961 100644
--- a/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_ultra.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_ultra.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Ultra Fine
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_verydraft.inst.cfg b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_verydraft.inst.cfg
index 77810c8809..1c462fd435 100644
--- a/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_verydraft.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_verydraft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Low Detail Draft
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_Draft_Quality.inst.cfg b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_Draft_Quality.inst.cfg
index d8aa07b779..c70ccc9946 100644
--- a/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_Draft_Quality.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_Draft_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_Fast_Quality.inst.cfg b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_Fast_Quality.inst.cfg
index a0620d532a..1bc10e2186 100644
--- a/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_Fast_Quality.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_Fast_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_High_Quality.inst.cfg b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_High_Quality.inst.cfg
index 7237dbc42a..24d5d5819f 100644
--- a/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_High_Quality.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_High_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Finer
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_Normal_Quality.inst.cfg b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_Normal_Quality.inst.cfg
index bcb360d310..0a884c80ca 100644
--- a/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_Normal_Quality.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_Normal_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_SuperDraft_Quality.inst.cfg b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_SuperDraft_Quality.inst.cfg
index 81954f86e7..9a0928186c 100644
--- a/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_SuperDraft_Quality.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_SuperDraft_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Lowest Quality Draft
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_ThickerDraft_Quality.inst.cfg b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_ThickerDraft_Quality.inst.cfg
index 1fe7ee22ab..994ec14a3e 100644
--- a/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_ThickerDraft_Quality.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_ThickerDraft_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Draft
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_Ultra_Quality.inst.cfg b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_Ultra_Quality.inst.cfg
index 89e2f43dad..813d156588 100644
--- a/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_Ultra_Quality.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_Ultra_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Ultra Fine
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_VeryDraft_Quality.inst.cfg b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_VeryDraft_Quality.inst.cfg
index c7fc09d9e8..0a461945f5 100644
--- a/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_VeryDraft_Quality.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_VeryDraft_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Low Detail Draft
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_draft.inst.cfg b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_draft.inst.cfg
index 80905c64f7..d5ccd3070b 100644
--- a/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_draft.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_draft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_fast.inst.cfg b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_fast.inst.cfg
index eab42b728e..aca8884024 100644
--- a/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_fast.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_fast.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_high.inst.cfg b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_high.inst.cfg
index 1261eff697..080a06d84f 100644
--- a/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_high.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_high.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Finer
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_normal.inst.cfg b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_normal.inst.cfg
index 6cf8709bf6..7f3222d229 100644
--- a/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_normal.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_superdraft.inst.cfg b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_superdraft.inst.cfg
index 3cf4efb73b..88777be100 100644
--- a/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_superdraft.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_superdraft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Lowest Quality Draft
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_thickerdraft.inst.cfg b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_thickerdraft.inst.cfg
index 22f27ab084..8880d6defa 100644
--- a/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_thickerdraft.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_thickerdraft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Draft
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_ultra.inst.cfg b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_ultra.inst.cfg
index b3b80d651f..945964cfac 100644
--- a/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_ultra.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_ultra.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Ultra Fine
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_verydraft.inst.cfg b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_verydraft.inst.cfg
index efd93a5381..fbebffde3a 100644
--- a/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_verydraft.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_verydraft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Low Detail Draft
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_draft.inst.cfg b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_draft.inst.cfg
index 05283042c2..be4f438df5 100644
--- a/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_draft.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_draft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_fast.inst.cfg b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_fast.inst.cfg
index 07319fe4db..c81f19a03e 100644
--- a/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_fast.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_fast.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_high.inst.cfg b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_high.inst.cfg
index 668040c851..714cd66c1f 100644
--- a/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_high.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_high.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Finer
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_normal.inst.cfg b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_normal.inst.cfg
index e9d907fabd..a314288364 100644
--- a/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_normal.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_superdraft.inst.cfg b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_superdraft.inst.cfg
index 5d1af5a430..4889d3fc7d 100644
--- a/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_superdraft.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_superdraft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Lowest Quality Draft
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_thickerdraft.inst.cfg b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_thickerdraft.inst.cfg
index 06c4b4f405..12a5c2ac6f 100644
--- a/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_thickerdraft.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_thickerdraft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Draft
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_ultra.inst.cfg b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_ultra.inst.cfg
index 821658554d..3b70904476 100644
--- a/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_ultra.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_ultra.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Ultra Fine
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_verydraft.inst.cfg b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_verydraft.inst.cfg
index 93dc04e13c..2ea13105ef 100644
--- a/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_verydraft.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_verydraft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Low Detail Draft
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_draft.inst.cfg b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_draft.inst.cfg
index 917e8b98a3..da8f6a7c9a 100644
--- a/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_draft.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_draft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_fast.inst.cfg b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_fast.inst.cfg
index aa98ea4de0..bc151b9635 100644
--- a/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_fast.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_fast.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_high.inst.cfg b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_high.inst.cfg
index 3db3af0db2..bc1101603e 100644
--- a/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_high.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_high.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Finer
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_normal.inst.cfg b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_normal.inst.cfg
index e39fd54550..932bfdf97a 100644
--- a/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_normal.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_superdraft.inst.cfg b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_superdraft.inst.cfg
index 6d9e99d365..77fb261821 100644
--- a/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_superdraft.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_superdraft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Lowest Quality Draft
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_thickerdraft.inst.cfg b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_thickerdraft.inst.cfg
index 5227aac686..096ff5c2f6 100644
--- a/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_thickerdraft.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_thickerdraft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Draft
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_ultra.inst.cfg b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_ultra.inst.cfg
index a58c05b004..9a3cc19a0c 100644
--- a/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_ultra.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_ultra.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Ultra Fine
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_verydraft.inst.cfg b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_verydraft.inst.cfg
index 0fe38fb5df..c9e0aae2c4 100644
--- a/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_verydraft.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_verydraft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Low Detail Draft
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_draft.inst.cfg b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_draft.inst.cfg
index 0b9f10a790..8f85c598bd 100644
--- a/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_draft.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_draft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_fast.inst.cfg b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_fast.inst.cfg
index d831890413..09e741ad07 100644
--- a/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_fast.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_fast.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_high.inst.cfg b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_high.inst.cfg
index f5049308f3..cf00fb02b0 100644
--- a/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_high.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_high.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Finer
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_normal.inst.cfg b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_normal.inst.cfg
index f63c87a52c..eaa85450d8 100644
--- a/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_normal.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_superdraft.inst.cfg b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_superdraft.inst.cfg
index a8bd9a5179..66f888cd6e 100644
--- a/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_superdraft.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_superdraft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Lowest Quality Draft
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_thickerdraft.inst.cfg b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_thickerdraft.inst.cfg
index df1ae97a16..c4c2a0c2d1 100644
--- a/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_thickerdraft.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_thickerdraft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Draft
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_ultra.inst.cfg b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_ultra.inst.cfg
index 3b3ec79692..b9b0fea26e 100644
--- a/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_ultra.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_ultra.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Ultra Fine
definition = monoprice_select_mini_v2
diff --git a/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_verydraft.inst.cfg b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_verydraft.inst.cfg
index 5e63de3952..bd6febd83c 100644
--- a/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_verydraft.inst.cfg
+++ b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_verydraft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Low Detail Draft
definition = monoprice_select_mini_v2
diff --git a/resources/quality/normal.inst.cfg b/resources/quality/normal.inst.cfg
index 2eeb91324f..0f1a4d6905 100644
--- a/resources/quality/normal.inst.cfg
+++ b/resources/quality/normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = fdmprinter
diff --git a/resources/quality/peopoly_moai/peopoly_moai_high.inst.cfg b/resources/quality/peopoly_moai/peopoly_moai_high.inst.cfg
index 208fa453fe..57c955f4b7 100644
--- a/resources/quality/peopoly_moai/peopoly_moai_high.inst.cfg
+++ b/resources/quality/peopoly_moai/peopoly_moai_high.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fine
definition = peopoly_moai
diff --git a/resources/quality/peopoly_moai/peopoly_moai_max.inst.cfg b/resources/quality/peopoly_moai/peopoly_moai_max.inst.cfg
index cc764009a9..4cce7e2d85 100644
--- a/resources/quality/peopoly_moai/peopoly_moai_max.inst.cfg
+++ b/resources/quality/peopoly_moai/peopoly_moai_max.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Maximum Quality
definition = peopoly_moai
diff --git a/resources/quality/peopoly_moai/peopoly_moai_normal.inst.cfg b/resources/quality/peopoly_moai/peopoly_moai_normal.inst.cfg
index 2baa70be1e..a1465a86c9 100644
--- a/resources/quality/peopoly_moai/peopoly_moai_normal.inst.cfg
+++ b/resources/quality/peopoly_moai/peopoly_moai_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = peopoly_moai
diff --git a/resources/quality/tevo_blackwidow/tevo_blackwidow_draft.inst.cfg b/resources/quality/tevo_blackwidow/tevo_blackwidow_draft.inst.cfg
index 2536cbba12..184205b1cd 100644
--- a/resources/quality/tevo_blackwidow/tevo_blackwidow_draft.inst.cfg
+++ b/resources/quality/tevo_blackwidow/tevo_blackwidow_draft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Draft
definition = tevo_blackwidow
diff --git a/resources/quality/tevo_blackwidow/tevo_blackwidow_high.inst.cfg b/resources/quality/tevo_blackwidow/tevo_blackwidow_high.inst.cfg
index 29599eaebc..d158af4123 100644
--- a/resources/quality/tevo_blackwidow/tevo_blackwidow_high.inst.cfg
+++ b/resources/quality/tevo_blackwidow/tevo_blackwidow_high.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High
definition = tevo_blackwidow
diff --git a/resources/quality/tevo_blackwidow/tevo_blackwidow_normal.inst.cfg b/resources/quality/tevo_blackwidow/tevo_blackwidow_normal.inst.cfg
index 98dbf5a79a..a44ff8bcdb 100644
--- a/resources/quality/tevo_blackwidow/tevo_blackwidow_normal.inst.cfg
+++ b/resources/quality/tevo_blackwidow/tevo_blackwidow_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = tevo_blackwidow
diff --git a/resources/quality/ultimaker2/um2_draft.inst.cfg b/resources/quality/ultimaker2/um2_draft.inst.cfg
index 88d3ed3520..dc761afc0b 100644
--- a/resources/quality/ultimaker2/um2_draft.inst.cfg
+++ b/resources/quality/ultimaker2/um2_draft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Draft Quality
definition = ultimaker2
diff --git a/resources/quality/ultimaker2/um2_fast.inst.cfg b/resources/quality/ultimaker2/um2_fast.inst.cfg
index 36e4fe03c2..04c24dc4c2 100644
--- a/resources/quality/ultimaker2/um2_fast.inst.cfg
+++ b/resources/quality/ultimaker2/um2_fast.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Low Quality
definition = ultimaker2
diff --git a/resources/quality/ultimaker2/um2_high.inst.cfg b/resources/quality/ultimaker2/um2_high.inst.cfg
index 2fb7ead455..1c83ea350b 100644
--- a/resources/quality/ultimaker2/um2_high.inst.cfg
+++ b/resources/quality/ultimaker2/um2_high.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fine
definition = ultimaker2
diff --git a/resources/quality/ultimaker2/um2_normal.inst.cfg b/resources/quality/ultimaker2/um2_normal.inst.cfg
index 51fe1f317c..bddbe6e243 100644
--- a/resources/quality/ultimaker2/um2_normal.inst.cfg
+++ b/resources/quality/ultimaker2/um2_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker2
diff --git a/resources/quality/ultimaker2_plus/pla_0.25_normal.inst.cfg b/resources/quality/ultimaker2_plus/pla_0.25_normal.inst.cfg
index 07e3dcd7db..eecaa2fa0f 100644
--- a/resources/quality/ultimaker2_plus/pla_0.25_normal.inst.cfg
+++ b/resources/quality/ultimaker2_plus/pla_0.25_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fine
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/pla_0.4_fast.inst.cfg b/resources/quality/ultimaker2_plus/pla_0.4_fast.inst.cfg
index 2a72e40d80..207d237fa3 100644
--- a/resources/quality/ultimaker2_plus/pla_0.4_fast.inst.cfg
+++ b/resources/quality/ultimaker2_plus/pla_0.4_fast.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/pla_0.4_high.inst.cfg b/resources/quality/ultimaker2_plus/pla_0.4_high.inst.cfg
index ce41158378..83ffa99d07 100644
--- a/resources/quality/ultimaker2_plus/pla_0.4_high.inst.cfg
+++ b/resources/quality/ultimaker2_plus/pla_0.4_high.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fine
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/pla_0.4_normal.inst.cfg b/resources/quality/ultimaker2_plus/pla_0.4_normal.inst.cfg
index edfe131ddf..683eab7166 100644
--- a/resources/quality/ultimaker2_plus/pla_0.4_normal.inst.cfg
+++ b/resources/quality/ultimaker2_plus/pla_0.4_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/pla_0.6_normal.inst.cfg b/resources/quality/ultimaker2_plus/pla_0.6_normal.inst.cfg
index 548c7478ba..80dea5f5fd 100644
--- a/resources/quality/ultimaker2_plus/pla_0.6_normal.inst.cfg
+++ b/resources/quality/ultimaker2_plus/pla_0.6_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/pla_0.8_normal.inst.cfg b/resources/quality/ultimaker2_plus/pla_0.8_normal.inst.cfg
index a257d3d6f8..5c898c74ec 100644
--- a/resources/quality/ultimaker2_plus/pla_0.8_normal.inst.cfg
+++ b/resources/quality/ultimaker2_plus/pla_0.8_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_abs_0.25_normal.inst.cfg b/resources/quality/ultimaker2_plus/um2p_abs_0.25_normal.inst.cfg
index e448b9d180..275a4595a0 100644
--- a/resources/quality/ultimaker2_plus/um2p_abs_0.25_normal.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_abs_0.25_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fine
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_abs_0.4_fast.inst.cfg b/resources/quality/ultimaker2_plus/um2p_abs_0.4_fast.inst.cfg
index 3fd2de3a36..a95417c711 100644
--- a/resources/quality/ultimaker2_plus/um2p_abs_0.4_fast.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_abs_0.4_fast.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_abs_0.4_high.inst.cfg b/resources/quality/ultimaker2_plus/um2p_abs_0.4_high.inst.cfg
index 2f119ae86b..97d35fe6bf 100644
--- a/resources/quality/ultimaker2_plus/um2p_abs_0.4_high.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_abs_0.4_high.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fine
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_abs_0.4_normal.inst.cfg b/resources/quality/ultimaker2_plus/um2p_abs_0.4_normal.inst.cfg
index 8416e69dba..162805f5c2 100644
--- a/resources/quality/ultimaker2_plus/um2p_abs_0.4_normal.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_abs_0.4_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_abs_0.6_normal.inst.cfg b/resources/quality/ultimaker2_plus/um2p_abs_0.6_normal.inst.cfg
index 393e630299..5291356b4e 100644
--- a/resources/quality/ultimaker2_plus/um2p_abs_0.6_normal.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_abs_0.6_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_abs_0.8_normal.inst.cfg b/resources/quality/ultimaker2_plus/um2p_abs_0.8_normal.inst.cfg
index d0dcbb0908..01c0b5467b 100644
--- a/resources/quality/ultimaker2_plus/um2p_abs_0.8_normal.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_abs_0.8_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_cpe_0.25_normal.inst.cfg b/resources/quality/ultimaker2_plus/um2p_cpe_0.25_normal.inst.cfg
index 1047b0ca39..1b656eb4f1 100644
--- a/resources/quality/ultimaker2_plus/um2p_cpe_0.25_normal.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_cpe_0.25_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fine
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_cpe_0.4_fast.inst.cfg b/resources/quality/ultimaker2_plus/um2p_cpe_0.4_fast.inst.cfg
index 7beaf2f229..8385800e15 100644
--- a/resources/quality/ultimaker2_plus/um2p_cpe_0.4_fast.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_cpe_0.4_fast.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_cpe_0.4_high.inst.cfg b/resources/quality/ultimaker2_plus/um2p_cpe_0.4_high.inst.cfg
index efd73655b1..6d810ee231 100644
--- a/resources/quality/ultimaker2_plus/um2p_cpe_0.4_high.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_cpe_0.4_high.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fine
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_cpe_0.4_normal.inst.cfg b/resources/quality/ultimaker2_plus/um2p_cpe_0.4_normal.inst.cfg
index 3badb3f191..1b6ab4edc5 100644
--- a/resources/quality/ultimaker2_plus/um2p_cpe_0.4_normal.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_cpe_0.4_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_cpe_0.6_normal.inst.cfg b/resources/quality/ultimaker2_plus/um2p_cpe_0.6_normal.inst.cfg
index 727d68eede..7f79c013b3 100644
--- a/resources/quality/ultimaker2_plus/um2p_cpe_0.6_normal.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_cpe_0.6_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_cpe_0.8_normal.inst.cfg b/resources/quality/ultimaker2_plus/um2p_cpe_0.8_normal.inst.cfg
index 1460d69b7a..df92d3e2dd 100644
--- a/resources/quality/ultimaker2_plus/um2p_cpe_0.8_normal.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_cpe_0.8_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_cpep_0.4_draft.inst.cfg b/resources/quality/ultimaker2_plus/um2p_cpep_0.4_draft.inst.cfg
index be33739e72..e83ae78d36 100644
--- a/resources/quality/ultimaker2_plus/um2p_cpep_0.4_draft.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_cpep_0.4_draft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_cpep_0.4_normal.inst.cfg b/resources/quality/ultimaker2_plus/um2p_cpep_0.4_normal.inst.cfg
index 58cf4bd0e3..ee0b659b91 100644
--- a/resources/quality/ultimaker2_plus/um2p_cpep_0.4_normal.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_cpep_0.4_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_cpep_0.6_draft.inst.cfg b/resources/quality/ultimaker2_plus/um2p_cpep_0.6_draft.inst.cfg
index fe4f895611..0773a81834 100644
--- a/resources/quality/ultimaker2_plus/um2p_cpep_0.6_draft.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_cpep_0.6_draft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_cpep_0.6_normal.inst.cfg b/resources/quality/ultimaker2_plus/um2p_cpep_0.6_normal.inst.cfg
index bcc507a3d9..4cea2db94e 100644
--- a/resources/quality/ultimaker2_plus/um2p_cpep_0.6_normal.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_cpep_0.6_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_cpep_0.8_draft.inst.cfg b/resources/quality/ultimaker2_plus/um2p_cpep_0.8_draft.inst.cfg
index c27d90ead8..27c3052885 100644
--- a/resources/quality/ultimaker2_plus/um2p_cpep_0.8_draft.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_cpep_0.8_draft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_cpep_0.8_normal.inst.cfg b/resources/quality/ultimaker2_plus/um2p_cpep_0.8_normal.inst.cfg
index 7eb8b1fd72..26d44f332b 100644
--- a/resources/quality/ultimaker2_plus/um2p_cpep_0.8_normal.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_cpep_0.8_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_nylon_0.25_high.inst.cfg b/resources/quality/ultimaker2_plus/um2p_nylon_0.25_high.inst.cfg
index 33041f89ae..d2c9c9d8d3 100644
--- a/resources/quality/ultimaker2_plus/um2p_nylon_0.25_high.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_nylon_0.25_high.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fine
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_nylon_0.25_normal.inst.cfg b/resources/quality/ultimaker2_plus/um2p_nylon_0.25_normal.inst.cfg
index 88974491cb..115e4dd0d3 100644
--- a/resources/quality/ultimaker2_plus/um2p_nylon_0.25_normal.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_nylon_0.25_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_nylon_0.4_fast.inst.cfg b/resources/quality/ultimaker2_plus/um2p_nylon_0.4_fast.inst.cfg
index 832fa12bc9..917715ef33 100644
--- a/resources/quality/ultimaker2_plus/um2p_nylon_0.4_fast.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_nylon_0.4_fast.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_nylon_0.4_normal.inst.cfg b/resources/quality/ultimaker2_plus/um2p_nylon_0.4_normal.inst.cfg
index c12573691e..125930991a 100644
--- a/resources/quality/ultimaker2_plus/um2p_nylon_0.4_normal.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_nylon_0.4_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_nylon_0.6_fast.inst.cfg b/resources/quality/ultimaker2_plus/um2p_nylon_0.6_fast.inst.cfg
index eaa4f86e23..3602182288 100644
--- a/resources/quality/ultimaker2_plus/um2p_nylon_0.6_fast.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_nylon_0.6_fast.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_nylon_0.6_normal.inst.cfg b/resources/quality/ultimaker2_plus/um2p_nylon_0.6_normal.inst.cfg
index 5e3db4db22..c41a8b3612 100644
--- a/resources/quality/ultimaker2_plus/um2p_nylon_0.6_normal.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_nylon_0.6_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_nylon_0.8_draft.inst.cfg b/resources/quality/ultimaker2_plus/um2p_nylon_0.8_draft.inst.cfg
index 38a71c9109..6e68d79f34 100644
--- a/resources/quality/ultimaker2_plus/um2p_nylon_0.8_draft.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_nylon_0.8_draft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_nylon_0.8_normal.inst.cfg b/resources/quality/ultimaker2_plus/um2p_nylon_0.8_normal.inst.cfg
index 9d20a6d91f..e01006cf58 100644
--- a/resources/quality/ultimaker2_plus/um2p_nylon_0.8_normal.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_nylon_0.8_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_pc_0.25_high.inst.cfg b/resources/quality/ultimaker2_plus/um2p_pc_0.25_high.inst.cfg
index 7829d7b7b9..91bc5e523a 100644
--- a/resources/quality/ultimaker2_plus/um2p_pc_0.25_high.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_pc_0.25_high.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fine
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_pc_0.25_normal.inst.cfg b/resources/quality/ultimaker2_plus/um2p_pc_0.25_normal.inst.cfg
index 168a308916..5407edf56d 100644
--- a/resources/quality/ultimaker2_plus/um2p_pc_0.25_normal.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_pc_0.25_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_pc_0.4_fast.inst.cfg b/resources/quality/ultimaker2_plus/um2p_pc_0.4_fast.inst.cfg
index 46a349d81f..5e0cef44cc 100644
--- a/resources/quality/ultimaker2_plus/um2p_pc_0.4_fast.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_pc_0.4_fast.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_pc_0.4_normal.inst.cfg b/resources/quality/ultimaker2_plus/um2p_pc_0.4_normal.inst.cfg
index 0c2450f512..9e400a46e7 100644
--- a/resources/quality/ultimaker2_plus/um2p_pc_0.4_normal.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_pc_0.4_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_pc_0.6_fast.inst.cfg b/resources/quality/ultimaker2_plus/um2p_pc_0.6_fast.inst.cfg
index 68528a0209..1741eb47e9 100644
--- a/resources/quality/ultimaker2_plus/um2p_pc_0.6_fast.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_pc_0.6_fast.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_pc_0.6_normal.inst.cfg b/resources/quality/ultimaker2_plus/um2p_pc_0.6_normal.inst.cfg
index a92110a149..17df729157 100644
--- a/resources/quality/ultimaker2_plus/um2p_pc_0.6_normal.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_pc_0.6_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_pc_0.8_draft.inst.cfg b/resources/quality/ultimaker2_plus/um2p_pc_0.8_draft.inst.cfg
index 8c8bff3846..87bad0bd64 100644
--- a/resources/quality/ultimaker2_plus/um2p_pc_0.8_draft.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_pc_0.8_draft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_pc_0.8_normal.inst.cfg b/resources/quality/ultimaker2_plus/um2p_pc_0.8_normal.inst.cfg
index db83d5181f..e1f208b2f1 100644
--- a/resources/quality/ultimaker2_plus/um2p_pc_0.8_normal.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_pc_0.8_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_pp_0.4_fast.inst.cfg b/resources/quality/ultimaker2_plus/um2p_pp_0.4_fast.inst.cfg
index b7474044f3..cbb4f2f881 100644
--- a/resources/quality/ultimaker2_plus/um2p_pp_0.4_fast.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_pp_0.4_fast.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_pp_0.4_normal.inst.cfg b/resources/quality/ultimaker2_plus/um2p_pp_0.4_normal.inst.cfg
index f6d2c38161..ee38faa33a 100644
--- a/resources/quality/ultimaker2_plus/um2p_pp_0.4_normal.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_pp_0.4_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_pp_0.6_draft.inst.cfg b/resources/quality/ultimaker2_plus/um2p_pp_0.6_draft.inst.cfg
index 99fdf8346c..9fe8c72397 100644
--- a/resources/quality/ultimaker2_plus/um2p_pp_0.6_draft.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_pp_0.6_draft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_pp_0.6_fast.inst.cfg b/resources/quality/ultimaker2_plus/um2p_pp_0.6_fast.inst.cfg
index 7a9ec8e4e3..ba9052818c 100644
--- a/resources/quality/ultimaker2_plus/um2p_pp_0.6_fast.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_pp_0.6_fast.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_pp_0.8_draft.inst.cfg b/resources/quality/ultimaker2_plus/um2p_pp_0.8_draft.inst.cfg
index 071c50c05b..2a6b4d1de8 100644
--- a/resources/quality/ultimaker2_plus/um2p_pp_0.8_draft.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_pp_0.8_draft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_pp_0.8_verydraft.inst.cfg b/resources/quality/ultimaker2_plus/um2p_pp_0.8_verydraft.inst.cfg
index cbea59a879..92f605f557 100644
--- a/resources/quality/ultimaker2_plus/um2p_pp_0.8_verydraft.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_pp_0.8_verydraft.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fast
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_tpu_0.25_high.inst.cfg b/resources/quality/ultimaker2_plus/um2p_tpu_0.25_high.inst.cfg
index 2ad062bd31..b6fe1515c0 100644
--- a/resources/quality/ultimaker2_plus/um2p_tpu_0.25_high.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_tpu_0.25_high.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fine
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_tpu_0.4_normal.inst.cfg b/resources/quality/ultimaker2_plus/um2p_tpu_0.4_normal.inst.cfg
index 56ce55ee96..27e09bdf10 100644
--- a/resources/quality/ultimaker2_plus/um2p_tpu_0.4_normal.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_tpu_0.4_normal.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker2_plus/um2p_tpu_0.6_fast.inst.cfg b/resources/quality/ultimaker2_plus/um2p_tpu_0.6_fast.inst.cfg
index 6bcc5fa31f..6d0b1ef8de 100644
--- a/resources/quality/ultimaker2_plus/um2p_tpu_0.6_fast.inst.cfg
+++ b/resources/quality/ultimaker2_plus/um2p_tpu_0.6_fast.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = ultimaker2_plus
diff --git a/resources/quality/ultimaker3/um3_aa0.25_ABS_Normal_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.25_ABS_Normal_Quality.inst.cfg
index 0b100d8717..c79c831984 100644
--- a/resources/quality/ultimaker3/um3_aa0.25_ABS_Normal_Quality.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.25_ABS_Normal_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_aa0.25_CPE_Normal_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.25_CPE_Normal_Quality.inst.cfg
index 77494d6339..83e77eb117 100644
--- a/resources/quality/ultimaker3/um3_aa0.25_CPE_Normal_Quality.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.25_CPE_Normal_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_aa0.25_Nylon_Normal_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.25_Nylon_Normal_Quality.inst.cfg
index 0187084ff8..c3ead92b51 100644
--- a/resources/quality/ultimaker3/um3_aa0.25_Nylon_Normal_Quality.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.25_Nylon_Normal_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_aa0.25_PC_Normal_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.25_PC_Normal_Quality.inst.cfg
index 0070419a4e..bd95837fee 100644
--- a/resources/quality/ultimaker3/um3_aa0.25_PC_Normal_Quality.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.25_PC_Normal_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine - Experimental
definition = ultimaker3
@@ -34,7 +34,6 @@ raft_airgap = 0.25
raft_interface_thickness = =max(layer_height * 1.5, 0.225)
retraction_count_max = 80
retraction_hop = 2
-retraction_hop_enabled = True
retraction_hop_only_when_collides = True
retraction_min_travel = 0.8
retraction_prime_speed = 15
diff --git a/resources/quality/ultimaker3/um3_aa0.25_PLA_Normal_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.25_PLA_Normal_Quality.inst.cfg
index b2347f1902..7f6fb9fa4d 100644
--- a/resources/quality/ultimaker3/um3_aa0.25_PLA_Normal_Quality.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.25_PLA_Normal_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_aa0.25_PP_Normal_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.25_PP_Normal_Quality.inst.cfg
index 732289dedf..6bdd94f58f 100644
--- a/resources/quality/ultimaker3/um3_aa0.25_PP_Normal_Quality.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.25_PP_Normal_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine - Experimental
definition = ultimaker3
@@ -38,7 +38,6 @@ retraction_count_max = 45
retraction_extra_prime_amount = 0.2
retraction_extrusion_window = 6.5
retraction_hop = 2
-retraction_hop_enabled = True
retraction_hop_only_when_collides = True
retraction_min_travel = 0.8
retraction_prime_speed = 13
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 a5cc4aaa0c..4b05943b64 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = ultimaker3
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 ba80ad588e..3b441942ac 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = ultimaker3
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 c84ef6dabe..79cdb4069d 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fine
definition = ultimaker3
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 f3c9f6a892..412bc4c5a8 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_aa0.4_BAM_Draft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_BAM_Draft_Print.inst.cfg
index fbc0c599ef..e4ef572efb 100644
--- a/resources/quality/ultimaker3/um3_aa0.4_BAM_Draft_Print.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.4_BAM_Draft_Print.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_aa0.4_BAM_Fast_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_BAM_Fast_Print.inst.cfg
index d6f64cb056..c896c74233 100644
--- a/resources/quality/ultimaker3/um3_aa0.4_BAM_Fast_Print.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.4_BAM_Fast_Print.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_aa0.4_BAM_Normal_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_BAM_Normal_Quality.inst.cfg
index 53fb52bff6..9fdcfb4cf3 100644
--- a/resources/quality/ultimaker3/um3_aa0.4_BAM_Normal_Quality.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.4_BAM_Normal_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker3
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 dd089294de..14263ee800 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = ultimaker3
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 373107d767..05d8988c36 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = ultimaker3
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 67544db97b..6a6fa94730 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fine
definition = ultimaker3
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 183d05ce60..402e23436a 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker3
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 74395628af..9a85ab9b3e 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = ultimaker3
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 dbcf7f3e42..7099062daf 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = ultimaker3
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 89b5210631..00adfcc2d7 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fine
definition = ultimaker3
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 12927714ec..590eddf43b 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_aa0.4_Nylon_Draft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_Nylon_Draft_Print.inst.cfg
index ff938df8f8..8ade8d4fe4 100644
--- a/resources/quality/ultimaker3/um3_aa0.4_Nylon_Draft_Print.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.4_Nylon_Draft_Print.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_aa0.4_Nylon_Fast_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_Nylon_Fast_Print.inst.cfg
index e9fd6df1e2..2fef1fb2a2 100644
--- a/resources/quality/ultimaker3/um3_aa0.4_Nylon_Fast_Print.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.4_Nylon_Fast_Print.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_aa0.4_Nylon_High_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_Nylon_High_Quality.inst.cfg
index 635cc573ea..9a320ecde3 100644
--- a/resources/quality/ultimaker3/um3_aa0.4_Nylon_High_Quality.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.4_Nylon_High_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fine
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_aa0.4_Nylon_Normal_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_Nylon_Normal_Quality.inst.cfg
index 778ace19e4..34efb4df95 100644
--- a/resources/quality/ultimaker3/um3_aa0.4_Nylon_Normal_Quality.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.4_Nylon_Normal_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker3
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 437e4676b3..79ae457587 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = ultimaker3
@@ -44,7 +44,6 @@ raft_interface_thickness = =max(layer_height * 1.5, 0.225)
retraction_count_max = 80
retraction_extrusion_window = 1
retraction_hop = 2
-retraction_hop_enabled = True
retraction_hop_only_when_collides = True
retraction_min_travel = 0.8
retraction_prime_speed = 15
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 320cf0a96f..2e07e65bbe 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = ultimaker3
@@ -43,7 +43,6 @@ raft_interface_thickness = =max(layer_height * 1.5, 0.225)
retraction_count_max = 80
retraction_extrusion_window = 1
retraction_hop = 2
-retraction_hop_enabled = True
retraction_hop_only_when_collides = True
retraction_min_travel = 0.8
retraction_prime_speed = 15
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 6d7e8f7976..2b1b1f850b 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fine
definition = ultimaker3
@@ -44,7 +44,6 @@ raft_interface_thickness = =max(layer_height * 1.5, 0.225)
retraction_count_max = 80
retraction_extrusion_window = 1
retraction_hop = 2
-retraction_hop_enabled = True
retraction_hop_only_when_collides = True
retraction_min_travel = 0.8
retraction_prime_speed = 15
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 c18e9dbbd5..437ef43878 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker3
@@ -41,7 +41,6 @@ raft_interface_thickness = =max(layer_height * 1.5, 0.225)
retraction_count_max = 80
retraction_extrusion_window = 1
retraction_hop = 2
-retraction_hop_enabled = True
retraction_hop_only_when_collides = True
retraction_min_travel = 0.8
retraction_prime_speed = 15
diff --git a/resources/quality/ultimaker3/um3_aa0.4_PLA_Draft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_PLA_Draft_Print.inst.cfg
index 24b7c51fbc..3805d7a396 100644
--- a/resources/quality/ultimaker3/um3_aa0.4_PLA_Draft_Print.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.4_PLA_Draft_Print.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_aa0.4_PLA_Fast_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_PLA_Fast_Print.inst.cfg
index 6b3d8642e0..3a8c6df621 100644
--- a/resources/quality/ultimaker3/um3_aa0.4_PLA_Fast_Print.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.4_PLA_Fast_Print.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_aa0.4_PLA_High_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_PLA_High_Quality.inst.cfg
index 6009031e0c..543391fd6b 100644
--- a/resources/quality/ultimaker3/um3_aa0.4_PLA_High_Quality.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.4_PLA_High_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fine
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_aa0.4_PLA_Normal_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_PLA_Normal_Quality.inst.cfg
index 3722134df6..c3c2a8d624 100644
--- a/resources/quality/ultimaker3/um3_aa0.4_PLA_Normal_Quality.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.4_PLA_Normal_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_aa0.4_PP_Draft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_PP_Draft_Print.inst.cfg
index 265e068598..1db33d9073 100644
--- a/resources/quality/ultimaker3/um3_aa0.4_PP_Draft_Print.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.4_PP_Draft_Print.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = ultimaker3
@@ -45,7 +45,6 @@ retraction_count_max = 12
retraction_extra_prime_amount = 0.8
retraction_extrusion_window = 1
retraction_hop = 2
-retraction_hop_enabled = True
retraction_hop_only_when_collides = True
retraction_min_travel = 0.8
retraction_prime_speed = 18
@@ -61,7 +60,6 @@ support_angle = 50
switch_extruder_prime_speed = 15
switch_extruder_retraction_amount = 20
switch_extruder_retraction_speeds = 35
-travel_avoid_distance = 3
wall_0_inset = 0
wall_line_width_x = =line_width
wall_thickness = =line_width * 3
diff --git a/resources/quality/ultimaker3/um3_aa0.4_PP_Fast_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_PP_Fast_Print.inst.cfg
index 7698c0a273..fe7da92bbe 100644
--- a/resources/quality/ultimaker3/um3_aa0.4_PP_Fast_Print.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.4_PP_Fast_Print.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = ultimaker3
@@ -44,7 +44,6 @@ retraction_count_max = 12
retraction_extra_prime_amount = 0.8
retraction_extrusion_window = 1
retraction_hop = 2
-retraction_hop_enabled = True
retraction_hop_only_when_collides = True
retraction_min_travel = 0.8
retraction_prime_speed = 18
@@ -61,7 +60,6 @@ switch_extruder_prime_speed = 15
switch_extruder_retraction_amount = 20
switch_extruder_retraction_speeds = 35
top_bottom_thickness = 1.1
-travel_avoid_distance = 3
wall_0_inset = 0
wall_line_width_x = =line_width
wall_thickness = =line_width * 3
diff --git a/resources/quality/ultimaker3/um3_aa0.4_PP_Normal_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_PP_Normal_Quality.inst.cfg
index 897ec993e7..8bbb0118b5 100644
--- a/resources/quality/ultimaker3/um3_aa0.4_PP_Normal_Quality.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.4_PP_Normal_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker3
@@ -43,7 +43,6 @@ retraction_count_max = 12
retraction_extra_prime_amount = 0.8
retraction_extrusion_window = 1
retraction_hop = 2
-retraction_hop_enabled = True
retraction_hop_only_when_collides = True
retraction_min_travel = 0.8
retraction_prime_speed = 18
@@ -60,7 +59,6 @@ switch_extruder_prime_speed = 15
switch_extruder_retraction_amount = 20
switch_extruder_retraction_speeds = 35
top_bottom_thickness = 1
-travel_avoid_distance = 3
wall_0_inset = 0
wall_line_width_x = =line_width
wall_thickness = =line_width * 3
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 d3654cb3e0..25e7c2c0ab 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = ultimaker3
@@ -43,7 +43,6 @@ retraction_count_max = 12
retraction_extra_prime_amount = 0.8
retraction_extrusion_window = 1
retraction_hop = 1.5
-retraction_hop_enabled = True
retraction_hop_only_when_collides = True
retraction_min_travel = =line_width * 2
retraction_prime_speed = 15
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 3686fefdff..0b7fa6501e 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = ultimaker3
@@ -44,7 +44,6 @@ retraction_count_max = 12
retraction_extra_prime_amount = 0.8
retraction_extrusion_window = 1
retraction_hop = 1.5
-retraction_hop_enabled = True
retraction_hop_only_when_collides = True
retraction_min_travel = =line_width * 2
retraction_prime_speed = 15
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 18d7a5275a..9afecb8987 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker3
@@ -41,7 +41,6 @@ retraction_count_max = 12
retraction_extra_prime_amount = 0.8
retraction_extrusion_window = 1
retraction_hop = 1.5
-retraction_hop_enabled = True
retraction_hop_only_when_collides = True
retraction_min_travel = =line_width * 2
retraction_prime_speed = 15
diff --git a/resources/quality/ultimaker3/um3_aa0.8_ABS_Draft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_ABS_Draft_Print.inst.cfg
index 78930b84e2..85e1415147 100644
--- a/resources/quality/ultimaker3/um3_aa0.8_ABS_Draft_Print.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.8_ABS_Draft_Print.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_aa0.8_ABS_Superdraft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_ABS_Superdraft_Print.inst.cfg
index 9470779bd4..3167473cc6 100644
--- a/resources/quality/ultimaker3/um3_aa0.8_ABS_Superdraft_Print.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.8_ABS_Superdraft_Print.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Sprint
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_aa0.8_ABS_Verydraft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_ABS_Verydraft_Print.inst.cfg
index d28e9b75b6..0414bf724c 100644
--- a/resources/quality/ultimaker3/um3_aa0.8_ABS_Verydraft_Print.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.8_ABS_Verydraft_Print.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fast
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_aa0.8_CPEP_Fast_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_CPEP_Fast_Print.inst.cfg
index 6f84174c5a..87728c2297 100644
--- a/resources/quality/ultimaker3/um3_aa0.8_CPEP_Fast_Print.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.8_CPEP_Fast_Print.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast - Experimental
definition = ultimaker3
@@ -36,4 +36,3 @@ support_bottom_distance = =support_z_distance
support_line_width = =round(line_width * 0.6 / 0.7, 2)
support_z_distance = =layer_height
top_bottom_thickness = 1.2
-travel_avoid_distance = 1.5
diff --git a/resources/quality/ultimaker3/um3_aa0.8_CPEP_Superdraft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_CPEP_Superdraft_Print.inst.cfg
index 09e59ee8ca..603f32a688 100644
--- a/resources/quality/ultimaker3/um3_aa0.8_CPEP_Superdraft_Print.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.8_CPEP_Superdraft_Print.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Sprint - Experimental
definition = ultimaker3
@@ -37,4 +37,3 @@ support_bottom_distance = =support_z_distance
support_line_width = =round(line_width * 0.6 / 0.7, 2)
support_z_distance = =layer_height
top_bottom_thickness = 1.2
-travel_avoid_distance = 1.5
diff --git a/resources/quality/ultimaker3/um3_aa0.8_CPEP_Verydraft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_CPEP_Verydraft_Print.inst.cfg
index 7b7d211c21..432d502245 100644
--- a/resources/quality/ultimaker3/um3_aa0.8_CPEP_Verydraft_Print.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.8_CPEP_Verydraft_Print.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fast - Experimental
definition = ultimaker3
@@ -37,4 +37,3 @@ support_bottom_distance = =support_z_distance
support_line_width = =round(line_width * 0.6 / 0.7, 2)
support_z_distance = =layer_height
top_bottom_thickness = 1.2
-travel_avoid_distance = 1.5
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 ecce20d9a2..fd138ae2f2 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = ultimaker3
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 a14d4601ee..feba0b3160 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Sprint
definition = ultimaker3
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 7a6cbc7251..1d9537097e 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fast
definition = ultimaker3
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 9254894df3..e205720539 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = ultimaker3
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 f6e2787e65..f2853a8781 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Sprint
definition = ultimaker3
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 794ccd006d..442323877c 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fast
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_aa0.8_PC_Fast_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_PC_Fast_Print.inst.cfg
index c0ae3852dc..d8460c6971 100644
--- a/resources/quality/ultimaker3/um3_aa0.8_PC_Fast_Print.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.8_PC_Fast_Print.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast - Experimental
definition = ultimaker3
@@ -29,4 +29,3 @@ speed_topbottom = =math.ceil(speed_print * 25 / 50)
speed_wall = =math.ceil(speed_print * 40 / 50)
speed_wall_0 = =math.ceil(speed_wall * 30 / 40)
support_line_width = =round(line_width * 0.6 / 0.7, 2)
-travel_avoid_distance = 3
diff --git a/resources/quality/ultimaker3/um3_aa0.8_PC_Superdraft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_PC_Superdraft_Print.inst.cfg
index ca58342847..53f613ec70 100644
--- a/resources/quality/ultimaker3/um3_aa0.8_PC_Superdraft_Print.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.8_PC_Superdraft_Print.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Sprint - Experimental
definition = ultimaker3
@@ -29,4 +29,3 @@ speed_topbottom = =math.ceil(speed_print * 25 / 50)
speed_wall = =math.ceil(speed_print * 40 / 50)
speed_wall_0 = =math.ceil(speed_wall * 30 / 40)
support_line_width = =round(line_width * 0.6 / 0.7, 2)
-travel_avoid_distance = 3
diff --git a/resources/quality/ultimaker3/um3_aa0.8_PC_Verydraft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_PC_Verydraft_Print.inst.cfg
index 6c69aa8ff5..a4fdaaa791 100644
--- a/resources/quality/ultimaker3/um3_aa0.8_PC_Verydraft_Print.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.8_PC_Verydraft_Print.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fast - Experimental
definition = ultimaker3
@@ -30,4 +30,3 @@ speed_topbottom = =math.ceil(speed_print * 25 / 50)
speed_wall = =math.ceil(speed_print * 40 / 50)
speed_wall_0 = =math.ceil(speed_wall * 30 / 40)
support_line_width = =round(line_width * 0.6 / 0.7, 2)
-travel_avoid_distance = 3
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 c03cc1ad5f..3293fa8382 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = ultimaker3
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 b1252a3662..b989d7e11a 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Sprint
definition = ultimaker3
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 191a7a4b1d..684eb11c5b 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
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fast
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_aa0.8_PP_Draft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_PP_Draft_Print.inst.cfg
index fb67666dc7..8ed11c1946 100644
--- a/resources/quality/ultimaker3/um3_aa0.8_PP_Draft_Print.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.8_PP_Draft_Print.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_aa0.8_PP_Superdraft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_PP_Superdraft_Print.inst.cfg
index 0fe9eb2bf9..01b947995d 100644
--- a/resources/quality/ultimaker3/um3_aa0.8_PP_Superdraft_Print.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.8_PP_Superdraft_Print.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Sprint
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_aa0.8_PP_Verydraft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_PP_Verydraft_Print.inst.cfg
index 5ec1311987..ba1aa89c91 100644
--- a/resources/quality/ultimaker3/um3_aa0.8_PP_Verydraft_Print.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.8_PP_Verydraft_Print.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fast
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_aa0.8_TPU_Draft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_TPU_Draft_Print.inst.cfg
index be3ea7e06d..e133d335f8 100644
--- a/resources/quality/ultimaker3/um3_aa0.8_TPU_Draft_Print.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.8_TPU_Draft_Print.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_aa0.8_TPU_Superdraft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_TPU_Superdraft_Print.inst.cfg
index cc0e963a35..c835de26f4 100644
--- a/resources/quality/ultimaker3/um3_aa0.8_TPU_Superdraft_Print.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.8_TPU_Superdraft_Print.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Sprint
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_aa0.8_TPU_Verydraft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_TPU_Verydraft_Print.inst.cfg
index 3310c09ba7..2e3ad5cf44 100644
--- a/resources/quality/ultimaker3/um3_aa0.8_TPU_Verydraft_Print.inst.cfg
+++ b/resources/quality/ultimaker3/um3_aa0.8_TPU_Verydraft_Print.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fast
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_bb0.4_PVA_Draft_Print.inst.cfg b/resources/quality/ultimaker3/um3_bb0.4_PVA_Draft_Print.inst.cfg
index eacdaccffe..4fff8be5ee 100644
--- a/resources/quality/ultimaker3/um3_bb0.4_PVA_Draft_Print.inst.cfg
+++ b/resources/quality/ultimaker3/um3_bb0.4_PVA_Draft_Print.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_bb0.4_PVA_Fast_Print.inst.cfg b/resources/quality/ultimaker3/um3_bb0.4_PVA_Fast_Print.inst.cfg
index c6ddd85181..8abce151bf 100644
--- a/resources/quality/ultimaker3/um3_bb0.4_PVA_Fast_Print.inst.cfg
+++ b/resources/quality/ultimaker3/um3_bb0.4_PVA_Fast_Print.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_bb0.4_PVA_High_Quality.inst.cfg b/resources/quality/ultimaker3/um3_bb0.4_PVA_High_Quality.inst.cfg
index 0b1d0f9f8d..0caf36e51b 100644
--- a/resources/quality/ultimaker3/um3_bb0.4_PVA_High_Quality.inst.cfg
+++ b/resources/quality/ultimaker3/um3_bb0.4_PVA_High_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fine
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_bb0.4_PVA_Normal_Quality.inst.cfg b/resources/quality/ultimaker3/um3_bb0.4_PVA_Normal_Quality.inst.cfg
index 9980e2b31c..0a8304a743 100644
--- a/resources/quality/ultimaker3/um3_bb0.4_PVA_Normal_Quality.inst.cfg
+++ b/resources/quality/ultimaker3/um3_bb0.4_PVA_Normal_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_bb0.8_PVA_Draft_Print.inst.cfg b/resources/quality/ultimaker3/um3_bb0.8_PVA_Draft_Print.inst.cfg
index edc960cc8c..d59f283eb6 100644
--- a/resources/quality/ultimaker3/um3_bb0.8_PVA_Draft_Print.inst.cfg
+++ b/resources/quality/ultimaker3/um3_bb0.8_PVA_Draft_Print.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_bb0.8_PVA_Superdraft_Print.inst.cfg b/resources/quality/ultimaker3/um3_bb0.8_PVA_Superdraft_Print.inst.cfg
index 6fb66dab14..4039d39d6a 100644
--- a/resources/quality/ultimaker3/um3_bb0.8_PVA_Superdraft_Print.inst.cfg
+++ b/resources/quality/ultimaker3/um3_bb0.8_PVA_Superdraft_Print.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Sprint
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_bb0.8_PVA_Verydraft_Print.inst.cfg b/resources/quality/ultimaker3/um3_bb0.8_PVA_Verydraft_Print.inst.cfg
index 12288919fe..247ee3d4da 100644
--- a/resources/quality/ultimaker3/um3_bb0.8_PVA_Verydraft_Print.inst.cfg
+++ b/resources/quality/ultimaker3/um3_bb0.8_PVA_Verydraft_Print.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fast
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_global_Draft_Quality.inst.cfg b/resources/quality/ultimaker3/um3_global_Draft_Quality.inst.cfg
index 9f13bbd3c2..49a514bff3 100644
--- a/resources/quality/ultimaker3/um3_global_Draft_Quality.inst.cfg
+++ b/resources/quality/ultimaker3/um3_global_Draft_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fast
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_global_Fast_Quality.inst.cfg b/resources/quality/ultimaker3/um3_global_Fast_Quality.inst.cfg
index 073ae82a07..69a94be93a 100644
--- a/resources/quality/ultimaker3/um3_global_Fast_Quality.inst.cfg
+++ b/resources/quality/ultimaker3/um3_global_Fast_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_global_High_Quality.inst.cfg b/resources/quality/ultimaker3/um3_global_High_Quality.inst.cfg
index 1daff86c49..2aebb18ab5 100644
--- a/resources/quality/ultimaker3/um3_global_High_Quality.inst.cfg
+++ b/resources/quality/ultimaker3/um3_global_High_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fine
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_global_Normal_Quality.inst.cfg b/resources/quality/ultimaker3/um3_global_Normal_Quality.inst.cfg
index 91c5bab8b7..5effc44fc5 100644
--- a/resources/quality/ultimaker3/um3_global_Normal_Quality.inst.cfg
+++ b/resources/quality/ultimaker3/um3_global_Normal_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Fine
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_global_Superdraft_Quality.inst.cfg b/resources/quality/ultimaker3/um3_global_Superdraft_Quality.inst.cfg
index da39b065d5..55f5796240 100644
--- a/resources/quality/ultimaker3/um3_global_Superdraft_Quality.inst.cfg
+++ b/resources/quality/ultimaker3/um3_global_Superdraft_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Sprint
definition = ultimaker3
diff --git a/resources/quality/ultimaker3/um3_global_Verydraft_Quality.inst.cfg b/resources/quality/ultimaker3/um3_global_Verydraft_Quality.inst.cfg
index 11e8315a7b..5d72a76c7c 100644
--- a/resources/quality/ultimaker3/um3_global_Verydraft_Quality.inst.cfg
+++ b/resources/quality/ultimaker3/um3_global_Verydraft_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extra Fast
definition = ultimaker3
diff --git a/resources/quality/vertex_delta_k8800/k8800_ABS_Extreme_Quality.inst.cfg b/resources/quality/vertex_delta_k8800/k8800_ABS_Extreme_Quality.inst.cfg
index 50a1ef3415..30c2749ac6 100644
--- a/resources/quality/vertex_delta_k8800/k8800_ABS_Extreme_Quality.inst.cfg
+++ b/resources/quality/vertex_delta_k8800/k8800_ABS_Extreme_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extreme
definition = vertex_delta_k8800
diff --git a/resources/quality/vertex_delta_k8800/k8800_ABS_High_Quality.inst.cfg b/resources/quality/vertex_delta_k8800/k8800_ABS_High_Quality.inst.cfg
index e650f11afd..102901ab3d 100644
--- a/resources/quality/vertex_delta_k8800/k8800_ABS_High_Quality.inst.cfg
+++ b/resources/quality/vertex_delta_k8800/k8800_ABS_High_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High
definition = vertex_delta_k8800
diff --git a/resources/quality/vertex_delta_k8800/k8800_ABS_Normal_Quality.inst.cfg b/resources/quality/vertex_delta_k8800/k8800_ABS_Normal_Quality.inst.cfg
index aa962190ce..f2e699f571 100644
--- a/resources/quality/vertex_delta_k8800/k8800_ABS_Normal_Quality.inst.cfg
+++ b/resources/quality/vertex_delta_k8800/k8800_ABS_Normal_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = vertex_delta_k8800
diff --git a/resources/quality/vertex_delta_k8800/k8800_PET_Extreme_Quality.inst.cfg b/resources/quality/vertex_delta_k8800/k8800_PET_Extreme_Quality.inst.cfg
index 29a4170adf..1f98ded1db 100644
--- a/resources/quality/vertex_delta_k8800/k8800_PET_Extreme_Quality.inst.cfg
+++ b/resources/quality/vertex_delta_k8800/k8800_PET_Extreme_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extreme
definition = vertex_delta_k8800
diff --git a/resources/quality/vertex_delta_k8800/k8800_PET_High_Quality.inst.cfg b/resources/quality/vertex_delta_k8800/k8800_PET_High_Quality.inst.cfg
index fc92590da1..f050c741c2 100644
--- a/resources/quality/vertex_delta_k8800/k8800_PET_High_Quality.inst.cfg
+++ b/resources/quality/vertex_delta_k8800/k8800_PET_High_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High
definition = vertex_delta_k8800
diff --git a/resources/quality/vertex_delta_k8800/k8800_PET_Normal_Quality.inst.cfg b/resources/quality/vertex_delta_k8800/k8800_PET_Normal_Quality.inst.cfg
index b02bfa5178..faeb8343fb 100644
--- a/resources/quality/vertex_delta_k8800/k8800_PET_Normal_Quality.inst.cfg
+++ b/resources/quality/vertex_delta_k8800/k8800_PET_Normal_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = vertex_delta_k8800
diff --git a/resources/quality/vertex_delta_k8800/k8800_PLA_Extreme_Quality.inst.cfg b/resources/quality/vertex_delta_k8800/k8800_PLA_Extreme_Quality.inst.cfg
index 3cdad7a06f..f5ac232d06 100644
--- a/resources/quality/vertex_delta_k8800/k8800_PLA_Extreme_Quality.inst.cfg
+++ b/resources/quality/vertex_delta_k8800/k8800_PLA_Extreme_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extreme
definition = vertex_delta_k8800
diff --git a/resources/quality/vertex_delta_k8800/k8800_PLA_High_Quality.inst.cfg b/resources/quality/vertex_delta_k8800/k8800_PLA_High_Quality.inst.cfg
index 192750ef71..63ed8389db 100644
--- a/resources/quality/vertex_delta_k8800/k8800_PLA_High_Quality.inst.cfg
+++ b/resources/quality/vertex_delta_k8800/k8800_PLA_High_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High
definition = vertex_delta_k8800
diff --git a/resources/quality/vertex_delta_k8800/k8800_PLA_Normal_Quality.inst.cfg b/resources/quality/vertex_delta_k8800/k8800_PLA_Normal_Quality.inst.cfg
index 1e519734bb..e84cbabade 100644
--- a/resources/quality/vertex_delta_k8800/k8800_PLA_Normal_Quality.inst.cfg
+++ b/resources/quality/vertex_delta_k8800/k8800_PLA_Normal_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = vertex_delta_k8800
diff --git a/resources/quality/vertex_delta_k8800/k8800_TPU_Extreme_Quality.inst.cfg b/resources/quality/vertex_delta_k8800/k8800_TPU_Extreme_Quality.inst.cfg
index 9da37f2148..a2e8a334f7 100644
--- a/resources/quality/vertex_delta_k8800/k8800_TPU_Extreme_Quality.inst.cfg
+++ b/resources/quality/vertex_delta_k8800/k8800_TPU_Extreme_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Extreme
definition = vertex_delta_k8800
diff --git a/resources/quality/vertex_delta_k8800/k8800_TPU_High_Quality.inst.cfg b/resources/quality/vertex_delta_k8800/k8800_TPU_High_Quality.inst.cfg
index f5583d32b9..4598332ba6 100644
--- a/resources/quality/vertex_delta_k8800/k8800_TPU_High_Quality.inst.cfg
+++ b/resources/quality/vertex_delta_k8800/k8800_TPU_High_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = High
definition = vertex_delta_k8800
diff --git a/resources/quality/vertex_delta_k8800/k8800_TPU_Normal_Quality.inst.cfg b/resources/quality/vertex_delta_k8800/k8800_TPU_Normal_Quality.inst.cfg
index a520eb2473..7a8a964b1c 100644
--- a/resources/quality/vertex_delta_k8800/k8800_TPU_Normal_Quality.inst.cfg
+++ b/resources/quality/vertex_delta_k8800/k8800_TPU_Normal_Quality.inst.cfg
@@ -1,5 +1,5 @@
[general]
-version = 2
+version = 3
name = Normal
definition = vertex_delta_k8800
diff --git a/resources/preset_setting_visibility_groups/advanced.cfg b/resources/setting_visibility/advanced.cfg
similarity index 100%
rename from resources/preset_setting_visibility_groups/advanced.cfg
rename to resources/setting_visibility/advanced.cfg
diff --git a/resources/preset_setting_visibility_groups/basic.cfg b/resources/setting_visibility/basic.cfg
similarity index 100%
rename from resources/preset_setting_visibility_groups/basic.cfg
rename to resources/setting_visibility/basic.cfg
diff --git a/resources/preset_setting_visibility_groups/expert.cfg b/resources/setting_visibility/expert.cfg
similarity index 99%
rename from resources/preset_setting_visibility_groups/expert.cfg
rename to resources/setting_visibility/expert.cfg
index e180b831d8..d6989f8b26 100644
--- a/resources/preset_setting_visibility_groups/expert.cfg
+++ b/resources/setting_visibility/expert.cfg
@@ -103,6 +103,7 @@ material_print_temperature_layer_0
material_initial_print_temperature
material_final_print_temperature
material_extrusion_cool_down_speed
+default_material_bed_temperature
material_bed_temperature
material_bed_temperature_layer_0
material_diameter
diff --git a/resources/shaders/camera_distance.shader b/resources/shaders/camera_distance.shader
new file mode 100644
index 0000000000..e6e894a2f6
--- /dev/null
+++ b/resources/shaders/camera_distance.shader
@@ -0,0 +1,83 @@
+[shaders]
+vertex =
+ uniform highp mat4 u_modelMatrix;
+ uniform highp mat4 u_viewProjectionMatrix;
+
+ attribute highp vec4 a_vertex;
+
+ varying highp vec3 v_vertex;
+
+ void main()
+ {
+ vec4 world_space_vert = u_modelMatrix * a_vertex;
+ gl_Position = u_viewProjectionMatrix * world_space_vert;
+
+ v_vertex = world_space_vert.xyz;
+ }
+
+fragment =
+ uniform highp vec3 u_viewPosition;
+
+ varying highp vec3 v_vertex;
+
+ void main()
+ {
+ highp float distance_to_camera = distance(v_vertex, u_viewPosition) * 1000.; // distance in micron
+
+ vec3 encoded; // encode float into 3 8-bit channels; this gives a precision of a micron at a range of up to ~16 meter
+ encoded.b = floor(distance_to_camera / 65536.0);
+ encoded.g = floor((distance_to_camera - encoded.b * 65536.0) / 256.0);
+ encoded.r = floor(distance_to_camera - encoded.b * 65536.0 - encoded.g * 256.0);
+
+ gl_FragColor.rgb = encoded / 255.;
+ gl_FragColor.a = 1.0;
+ }
+
+vertex41core =
+ #version 410
+ uniform highp mat4 u_modelMatrix;
+ uniform highp mat4 u_viewProjectionMatrix;
+
+ in highp vec4 a_vertex;
+
+ out highp vec3 v_vertex;
+
+ void main()
+ {
+ vec4 world_space_vert = u_modelMatrix * a_vertex;
+ gl_Position = u_viewProjectionMatrix * world_space_vert;
+
+ v_vertex = world_space_vert.xyz;
+ }
+
+fragment41core =
+ #version 410
+ uniform highp vec3 u_viewPosition;
+
+ in highp vec3 v_vertex;
+
+ out vec4 frag_color;
+
+ void main()
+ {
+ highp float distance_to_camera = distance(v_vertex, u_viewPosition) * 1000.; // distance in micron
+
+ vec3 encoded; // encode float into 3 8-bit channels; this gives a precision of a micron at a range of up to ~16 meter
+ encoded.r = floor(distance_to_camera / 65536.0);
+ encoded.g = floor((distance_to_camera - encoded.r * 65536.0) / 256.0);
+ encoded.b = floor(distance_to_camera - encoded.r * 65536.0 - encoded.g * 256.0);
+
+ frag_color.rgb = encoded / 255.;
+ frag_color.a = 1.0;
+ }
+
+[defaults]
+
+[bindings]
+u_modelMatrix = model_matrix
+u_viewProjectionMatrix = view_projection_matrix
+u_normalMatrix = normal_matrix
+u_viewPosition = view_position
+
+[attributes]
+a_vertex = vertex
diff --git a/resources/themes/cura-dark/theme.json b/resources/themes/cura-dark/theme.json
index 80a5eec09c..26e6c2ac8b 100644
--- a/resources/themes/cura-dark/theme.json
+++ b/resources/themes/cura-dark/theme.json
@@ -84,16 +84,16 @@
"tab_background": [39, 44, 48, 255],
"action_button": [39, 44, 48, 255],
- "action_button_text": [255, 255, 255, 101],
+ "action_button_text": [255, 255, 255, 200],
"action_button_border": [255, 255, 255, 30],
"action_button_hovered": [39, 44, 48, 255],
"action_button_hovered_text": [255, 255, 255, 255],
"action_button_hovered_border": [255, 255, 255, 30],
"action_button_active": [39, 44, 48, 30],
"action_button_active_text": [255, 255, 255, 255],
- "action_button_active_border": [255, 255, 255, 30],
+ "action_button_active_border": [255, 255, 255, 100],
"action_button_disabled": [39, 44, 48, 255],
- "action_button_disabled_text": [255, 255, 255, 101],
+ "action_button_disabled_text": [255, 255, 255, 80],
"action_button_disabled_border": [255, 255, 255, 30],
"scrollbar_background": [39, 44, 48, 0],
@@ -207,6 +207,14 @@
"layerview_support_interface": [64, 192, 255, 255],
"layerview_nozzle": [181, 166, 66, 120],
+ "configuration_item": [0, 0, 0, 0],
+ "configuration_item_active": [12, 169, 227, 179],
+ "configuration_item_text": [255, 255, 255, 255],
+ "configuration_item_text_active": [255, 255, 255, 255],
+ "configuration_item_border": [255, 255, 255, 255],
+ "configuration_item_border_active": [12, 169, 227, 179],
+ "configuration_item_border_hover": [12, 169, 227, 179],
+
"material_compatibility_warning": [255, 255, 255, 255],
"quality_slider_unavailable": [179, 179, 179, 255],
diff --git a/resources/themes/cura-light/icons/buildplate.svg b/resources/themes/cura-light/icons/buildplate.svg
new file mode 100644
index 0000000000..9e61296958
--- /dev/null
+++ b/resources/themes/cura-light/icons/buildplate.svg
@@ -0,0 +1,17 @@
+
+
+
+ icn_buildplate
+ Created with Sketch.
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/resources/themes/cura-light/icons/connected.svg b/resources/themes/cura-light/icons/connected.svg
new file mode 100644
index 0000000000..18423bb6c4
--- /dev/null
+++ b/resources/themes/cura-light/icons/connected.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/resources/themes/cura-light/icons/disconnected.svg b/resources/themes/cura-light/icons/disconnected.svg
new file mode 100644
index 0000000000..019dff117e
--- /dev/null
+++ b/resources/themes/cura-light/icons/disconnected.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/resources/themes/cura-light/icons/menu.svg b/resources/themes/cura-light/icons/menu.svg
new file mode 100644
index 0000000000..85fbfb072c
--- /dev/null
+++ b/resources/themes/cura-light/icons/menu.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/resources/themes/cura-light/icons/printer_group.svg b/resources/themes/cura-light/icons/printer_group.svg
new file mode 100644
index 0000000000..614bea90b8
--- /dev/null
+++ b/resources/themes/cura-light/icons/printer_group.svg
@@ -0,0 +1,12 @@
+
+
+
+ icn_groupPrinters
+ Created with Sketch.
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/resources/themes/cura-light/icons/printer_single.svg b/resources/themes/cura-light/icons/printer_single.svg
new file mode 100644
index 0000000000..f7dc83987d
--- /dev/null
+++ b/resources/themes/cura-light/icons/printer_single.svg
@@ -0,0 +1,14 @@
+
+
+
+ icn_singlePrinter
+ Created with Sketch.
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json
index 8c8e6d1c47..4f4b2306a8 100644
--- a/resources/themes/cura-light/theme.json
+++ b/resources/themes/cura-light/theme.json
@@ -14,6 +14,16 @@
"weight": 50,
"family": "Noto Sans"
},
+ "medium": {
+ "size": 1.16,
+ "weight": 50,
+ "family": "Noto Sans"
+ },
+ "medium_bold": {
+ "size": 1.16,
+ "weight": 63,
+ "family": "Noto Sans"
+ },
"default": {
"size": 1.0,
"weight": 50,
@@ -289,7 +299,21 @@
"layerview_move_combing": [0, 0, 255, 255],
"layerview_move_retraction": [128, 128, 255, 255],
"layerview_support_interface": [64, 192, 255, 255],
- "layerview_nozzle": [181, 166, 66, 50]
+ "layerview_nozzle": [181, 166, 66, 50],
+
+ "configuration_item": [255, 255, 255, 0],
+ "configuration_item_active": [12, 169, 227, 32],
+ "configuration_item_text": [0, 0, 0, 255],
+ "configuration_item_text_active": [0, 0, 0, 255],
+ "configuration_item_border": [127, 127, 127, 255],
+ "configuration_item_border_active": [12, 169, 227, 32],
+ "configuration_item_border_hover": [12, 169, 227, 255],
+
+ "tab_status_connected": [12, 169, 227, 255],
+ "tab_status_disconnected": [200, 200, 200, 255],
+
+ "printer_config_matched": [12, 169, 227, 255],
+ "printer_config_mismatch": [127, 127, 127, 255]
},
"sizes": {
@@ -342,6 +366,9 @@
"small_button": [2, 2],
"small_button_icon": [1.5, 1.5],
+ "printer_status_icon": [1.8, 1.8],
+ "printer_sync_icon": [1.2, 1.2],
+
"topbar_logo_right_margin": [3, 0],
"topbar_button": [8, 4],
"topbar_button_icon": [1.2, 1.2],
@@ -384,6 +411,8 @@
"save_button_save_to_button": [0.3, 2.7],
"save_button_specs_icons": [1.4, 1.4],
+ "monitor_preheat_temperature_control": [4.5, 2.0],
+
"modal_window_minimum": [60.0, 45],
"license_window_minimum": [45, 45],
"wizard_progress": [10.0, 0.0],
diff --git a/resources/variants/cartesio_0.25.inst.cfg b/resources/variants/cartesio_0.25.inst.cfg
index 014069451c..a3fbe67606 100644
--- a/resources/variants/cartesio_0.25.inst.cfg
+++ b/resources/variants/cartesio_0.25.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.25 mm
-version = 2
+version = 3
definition = cartesio
[metadata]
diff --git a/resources/variants/cartesio_0.4.inst.cfg b/resources/variants/cartesio_0.4.inst.cfg
index 7b5dccd980..d5fbc4dc26 100644
--- a/resources/variants/cartesio_0.4.inst.cfg
+++ b/resources/variants/cartesio_0.4.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.4 mm
-version = 2
+version = 3
definition = cartesio
[metadata]
diff --git a/resources/variants/cartesio_0.8.inst.cfg b/resources/variants/cartesio_0.8.inst.cfg
index 70271dbf75..a309c47424 100644
--- a/resources/variants/cartesio_0.8.inst.cfg
+++ b/resources/variants/cartesio_0.8.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.8 mm
-version = 2
+version = 3
definition = cartesio
[metadata]
diff --git a/resources/variants/fabtotum_hyb35.inst.cfg b/resources/variants/fabtotum_hyb35.inst.cfg
index e036b2e474..d96189e88e 100644
--- a/resources/variants/fabtotum_hyb35.inst.cfg
+++ b/resources/variants/fabtotum_hyb35.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = Hybrid 0.35 mm
-version = 2
+version = 3
definition = fabtotum
[metadata]
diff --git a/resources/variants/fabtotum_lite04.inst.cfg b/resources/variants/fabtotum_lite04.inst.cfg
index defa6692a8..0309b81733 100644
--- a/resources/variants/fabtotum_lite04.inst.cfg
+++ b/resources/variants/fabtotum_lite04.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = Lite 0.4 mm
-version = 2
+version = 3
definition = fabtotum
[metadata]
diff --git a/resources/variants/fabtotum_lite06.inst.cfg b/resources/variants/fabtotum_lite06.inst.cfg
index d269092b42..c92e621bd2 100644
--- a/resources/variants/fabtotum_lite06.inst.cfg
+++ b/resources/variants/fabtotum_lite06.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = Lite 0.6 mm
-version = 2
+version = 3
definition = fabtotum
[metadata]
diff --git a/resources/variants/fabtotum_pro02.inst.cfg b/resources/variants/fabtotum_pro02.inst.cfg
index b705929c35..29d245a24a 100644
--- a/resources/variants/fabtotum_pro02.inst.cfg
+++ b/resources/variants/fabtotum_pro02.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = Pro 0.2 mm
-version = 2
+version = 3
definition = fabtotum
[metadata]
diff --git a/resources/variants/fabtotum_pro04.inst.cfg b/resources/variants/fabtotum_pro04.inst.cfg
index b835312324..42be71b89d 100644
--- a/resources/variants/fabtotum_pro04.inst.cfg
+++ b/resources/variants/fabtotum_pro04.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = Pro 0.4 mm
-version = 2
+version = 3
definition = fabtotum
[metadata]
diff --git a/resources/variants/fabtotum_pro06.inst.cfg b/resources/variants/fabtotum_pro06.inst.cfg
index 140b6618cf..9c17d83f47 100644
--- a/resources/variants/fabtotum_pro06.inst.cfg
+++ b/resources/variants/fabtotum_pro06.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = Pro 0.6 mm
-version = 2
+version = 3
definition = fabtotum
[metadata]
diff --git a/resources/variants/fabtotum_pro08.inst.cfg b/resources/variants/fabtotum_pro08.inst.cfg
index 421cd0b662..2fc5507024 100644
--- a/resources/variants/fabtotum_pro08.inst.cfg
+++ b/resources/variants/fabtotum_pro08.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = Pro 0.8 mm
-version = 2
+version = 3
definition = fabtotum
[metadata]
diff --git a/resources/variants/gmax15plus_025_e3d.inst.cfg b/resources/variants/gmax15plus_025_e3d.inst.cfg
index 3c7b2e4949..53e6a14b23 100644
--- a/resources/variants/gmax15plus_025_e3d.inst.cfg
+++ b/resources/variants/gmax15plus_025_e3d.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.25mm E3D (Difficult)
-version = 2
+version = 3
definition = gmax15plus
[metadata]
diff --git a/resources/variants/gmax15plus_04_e3d.inst.cfg b/resources/variants/gmax15plus_04_e3d.inst.cfg
index e9c0cf1b18..a0b0d58b92 100644
--- a/resources/variants/gmax15plus_04_e3d.inst.cfg
+++ b/resources/variants/gmax15plus_04_e3d.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.4mm E3D
-version = 2
+version = 3
definition = gmax15plus
[metadata]
diff --git a/resources/variants/gmax15plus_05_e3d.inst.cfg b/resources/variants/gmax15plus_05_e3d.inst.cfg
index 8575f3c539..333ab55f81 100644
--- a/resources/variants/gmax15plus_05_e3d.inst.cfg
+++ b/resources/variants/gmax15plus_05_e3d.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.5mm E3D (Default)
-version = 2
+version = 3
definition = gmax15plus
[metadata]
diff --git a/resources/variants/gmax15plus_05_jhead.inst.cfg b/resources/variants/gmax15plus_05_jhead.inst.cfg
index 14d6ab5e9d..51902ffa0c 100644
--- a/resources/variants/gmax15plus_05_jhead.inst.cfg
+++ b/resources/variants/gmax15plus_05_jhead.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.5mm J-Head
-version = 2
+version = 3
definition = gmax15plus
[metadata]
diff --git a/resources/variants/gmax15plus_06_e3d.inst.cfg b/resources/variants/gmax15plus_06_e3d.inst.cfg
index 5d3f5c63a9..3452e5e81f 100644
--- a/resources/variants/gmax15plus_06_e3d.inst.cfg
+++ b/resources/variants/gmax15plus_06_e3d.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.6mm E3D
-version = 2
+version = 3
definition = gmax15plus
[metadata]
diff --git a/resources/variants/gmax15plus_08_e3d.inst.cfg b/resources/variants/gmax15plus_08_e3d.inst.cfg
index af08cd2c7c..cdee755efc 100644
--- a/resources/variants/gmax15plus_08_e3d.inst.cfg
+++ b/resources/variants/gmax15plus_08_e3d.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.8mm E3D
-version = 2
+version = 3
definition = gmax15plus
[metadata]
diff --git a/resources/variants/gmax15plus_10_jhead.inst.cfg b/resources/variants/gmax15plus_10_jhead.inst.cfg
index df61d9729f..ee60c71cc2 100644
--- a/resources/variants/gmax15plus_10_jhead.inst.cfg
+++ b/resources/variants/gmax15plus_10_jhead.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 1.0mm J-Head
-version = 2
+version = 3
definition = gmax15plus
[metadata]
diff --git a/resources/variants/gmax15plus_dual_025_e3d.inst.cfg b/resources/variants/gmax15plus_dual_025_e3d.inst.cfg
index 07bdfca9b7..002af1a0c5 100644
--- a/resources/variants/gmax15plus_dual_025_e3d.inst.cfg
+++ b/resources/variants/gmax15plus_dual_025_e3d.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.25mm E3D (Difficult)
-version = 2
+version = 3
definition = gmax15plus_dual
[metadata]
diff --git a/resources/variants/gmax15plus_dual_04_e3d.inst.cfg b/resources/variants/gmax15plus_dual_04_e3d.inst.cfg
index 966fe9f224..a98637e9e1 100644
--- a/resources/variants/gmax15plus_dual_04_e3d.inst.cfg
+++ b/resources/variants/gmax15plus_dual_04_e3d.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.4mm E3D
-version = 2
+version = 3
definition = gmax15plus_dual
[metadata]
diff --git a/resources/variants/gmax15plus_dual_05_e3d.inst.cfg b/resources/variants/gmax15plus_dual_05_e3d.inst.cfg
index 85464bd733..dc7d711043 100644
--- a/resources/variants/gmax15plus_dual_05_e3d.inst.cfg
+++ b/resources/variants/gmax15plus_dual_05_e3d.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.5mm E3D (Default)
-version = 2
+version = 3
definition = gmax15plus_dual
[metadata]
diff --git a/resources/variants/gmax15plus_dual_05_jhead.inst.cfg b/resources/variants/gmax15plus_dual_05_jhead.inst.cfg
index 689c76541a..fc4f17d057 100644
--- a/resources/variants/gmax15plus_dual_05_jhead.inst.cfg
+++ b/resources/variants/gmax15plus_dual_05_jhead.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.5mm J-Head
-version = 2
+version = 3
definition = gmax15plus_dual
[metadata]
diff --git a/resources/variants/gmax15plus_dual_06_e3d.inst.cfg b/resources/variants/gmax15plus_dual_06_e3d.inst.cfg
index 57641a4244..506f2df06e 100644
--- a/resources/variants/gmax15plus_dual_06_e3d.inst.cfg
+++ b/resources/variants/gmax15plus_dual_06_e3d.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.6mm E3D
-version = 2
+version = 3
definition = gmax15plus_dual
[metadata]
diff --git a/resources/variants/gmax15plus_dual_08_e3d.inst.cfg b/resources/variants/gmax15plus_dual_08_e3d.inst.cfg
index 11523ccd67..d008df70f6 100644
--- a/resources/variants/gmax15plus_dual_08_e3d.inst.cfg
+++ b/resources/variants/gmax15plus_dual_08_e3d.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.8mm E3D
-version = 2
+version = 3
definition = gmax15plus_dual
[metadata]
diff --git a/resources/variants/gmax15plus_dual_10_jhead.inst.cfg b/resources/variants/gmax15plus_dual_10_jhead.inst.cfg
index 26721d0169..825810997e 100644
--- a/resources/variants/gmax15plus_dual_10_jhead.inst.cfg
+++ b/resources/variants/gmax15plus_dual_10_jhead.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 1.0mm J-Head
-version = 2
+version = 3
definition = gmax15plus_dual
[metadata]
diff --git a/resources/variants/imade3d_jellybox_0.4.inst.cfg b/resources/variants/imade3d_jellybox_0.4.inst.cfg
index 5baa8123f2..99b55866c7 100644
--- a/resources/variants/imade3d_jellybox_0.4.inst.cfg
+++ b/resources/variants/imade3d_jellybox_0.4.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.4 mm
-version = 2
+version = 3
definition = imade3d_jellybox
[metadata]
diff --git a/resources/variants/imade3d_jellybox_0.4_2-fans.inst.cfg b/resources/variants/imade3d_jellybox_0.4_2-fans.inst.cfg
index 5d1a01c366..890c394a36 100644
--- a/resources/variants/imade3d_jellybox_0.4_2-fans.inst.cfg
+++ b/resources/variants/imade3d_jellybox_0.4_2-fans.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.4 mm 2-fans
-version = 2
+version = 3
definition = imade3d_jellybox
[metadata]
diff --git a/resources/variants/ultimaker2_0.25.inst.cfg b/resources/variants/ultimaker2_0.25.inst.cfg
index 2b1b04f123..c4d778e5cf 100644
--- a/resources/variants/ultimaker2_0.25.inst.cfg
+++ b/resources/variants/ultimaker2_0.25.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.25 mm
-version = 2
+version = 3
definition = ultimaker2
[metadata]
diff --git a/resources/variants/ultimaker2_0.4.inst.cfg b/resources/variants/ultimaker2_0.4.inst.cfg
index 8886992f6f..2ce766a1ac 100644
--- a/resources/variants/ultimaker2_0.4.inst.cfg
+++ b/resources/variants/ultimaker2_0.4.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.4 mm
-version = 2
+version = 3
definition = ultimaker2
[metadata]
diff --git a/resources/variants/ultimaker2_0.6.inst.cfg b/resources/variants/ultimaker2_0.6.inst.cfg
index 42de9d3ee8..0c5bdca240 100644
--- a/resources/variants/ultimaker2_0.6.inst.cfg
+++ b/resources/variants/ultimaker2_0.6.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.6 mm
-version = 2
+version = 3
definition = ultimaker2
[metadata]
diff --git a/resources/variants/ultimaker2_0.8.inst.cfg b/resources/variants/ultimaker2_0.8.inst.cfg
index df2ca88ba2..fd11520eca 100644
--- a/resources/variants/ultimaker2_0.8.inst.cfg
+++ b/resources/variants/ultimaker2_0.8.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.8 mm
-version = 2
+version = 3
definition = ultimaker2
[metadata]
diff --git a/resources/variants/ultimaker2_extended_0.25.inst.cfg b/resources/variants/ultimaker2_extended_0.25.inst.cfg
index f04a28e5ba..b8a31641e3 100644
--- a/resources/variants/ultimaker2_extended_0.25.inst.cfg
+++ b/resources/variants/ultimaker2_extended_0.25.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.25 mm
-version = 2
+version = 3
definition = ultimaker2_extended
[metadata]
diff --git a/resources/variants/ultimaker2_extended_0.4.inst.cfg b/resources/variants/ultimaker2_extended_0.4.inst.cfg
index c54202750a..dbceac2890 100644
--- a/resources/variants/ultimaker2_extended_0.4.inst.cfg
+++ b/resources/variants/ultimaker2_extended_0.4.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.4 mm
-version = 2
+version = 3
definition = ultimaker2_extended
[metadata]
diff --git a/resources/variants/ultimaker2_extended_0.6.inst.cfg b/resources/variants/ultimaker2_extended_0.6.inst.cfg
index 67176632aa..6fbb489160 100644
--- a/resources/variants/ultimaker2_extended_0.6.inst.cfg
+++ b/resources/variants/ultimaker2_extended_0.6.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.6 mm
-version = 2
+version = 3
definition = ultimaker2_extended
[metadata]
diff --git a/resources/variants/ultimaker2_extended_0.8.inst.cfg b/resources/variants/ultimaker2_extended_0.8.inst.cfg
index 61917309ef..94b38de8f2 100644
--- a/resources/variants/ultimaker2_extended_0.8.inst.cfg
+++ b/resources/variants/ultimaker2_extended_0.8.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.8 mm
-version = 2
+version = 3
definition = ultimaker2_extended
[metadata]
diff --git a/resources/variants/ultimaker2_extended_plus_0.25.inst.cfg b/resources/variants/ultimaker2_extended_plus_0.25.inst.cfg
index 6386d71f50..89916470bb 100644
--- a/resources/variants/ultimaker2_extended_plus_0.25.inst.cfg
+++ b/resources/variants/ultimaker2_extended_plus_0.25.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.25 mm
-version = 2
+version = 3
definition = ultimaker2_extended_plus
[metadata]
diff --git a/resources/variants/ultimaker2_extended_plus_0.4.inst.cfg b/resources/variants/ultimaker2_extended_plus_0.4.inst.cfg
index 1426f733cc..0de416da11 100644
--- a/resources/variants/ultimaker2_extended_plus_0.4.inst.cfg
+++ b/resources/variants/ultimaker2_extended_plus_0.4.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.4 mm
-version = 2
+version = 3
definition = ultimaker2_extended_plus
[metadata]
diff --git a/resources/variants/ultimaker2_extended_plus_0.6.inst.cfg b/resources/variants/ultimaker2_extended_plus_0.6.inst.cfg
index 5ea4072022..4e6ace5a59 100644
--- a/resources/variants/ultimaker2_extended_plus_0.6.inst.cfg
+++ b/resources/variants/ultimaker2_extended_plus_0.6.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.6 mm
-version = 2
+version = 3
definition = ultimaker2_extended_plus
[metadata]
diff --git a/resources/variants/ultimaker2_extended_plus_0.8.inst.cfg b/resources/variants/ultimaker2_extended_plus_0.8.inst.cfg
index 69b2b9b0d0..7540d5783a 100644
--- a/resources/variants/ultimaker2_extended_plus_0.8.inst.cfg
+++ b/resources/variants/ultimaker2_extended_plus_0.8.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.8 mm
-version = 2
+version = 3
definition = ultimaker2_extended_plus
[metadata]
diff --git a/resources/variants/ultimaker2_plus_0.25.inst.cfg b/resources/variants/ultimaker2_plus_0.25.inst.cfg
index 2b40656cf4..d59476a4f7 100644
--- a/resources/variants/ultimaker2_plus_0.25.inst.cfg
+++ b/resources/variants/ultimaker2_plus_0.25.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.25 mm
-version = 2
+version = 3
definition = ultimaker2_plus
[metadata]
diff --git a/resources/variants/ultimaker2_plus_0.4.inst.cfg b/resources/variants/ultimaker2_plus_0.4.inst.cfg
index 0aaf4f4e5a..a188d10d4a 100644
--- a/resources/variants/ultimaker2_plus_0.4.inst.cfg
+++ b/resources/variants/ultimaker2_plus_0.4.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.4 mm
-version = 2
+version = 3
definition = ultimaker2_plus
[metadata]
diff --git a/resources/variants/ultimaker2_plus_0.6.inst.cfg b/resources/variants/ultimaker2_plus_0.6.inst.cfg
index 7988a949bc..b3aad334c3 100644
--- a/resources/variants/ultimaker2_plus_0.6.inst.cfg
+++ b/resources/variants/ultimaker2_plus_0.6.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.6 mm
-version = 2
+version = 3
definition = ultimaker2_plus
[metadata]
diff --git a/resources/variants/ultimaker2_plus_0.8.inst.cfg b/resources/variants/ultimaker2_plus_0.8.inst.cfg
index 1a7824b0b1..3c5dec79f6 100644
--- a/resources/variants/ultimaker2_plus_0.8.inst.cfg
+++ b/resources/variants/ultimaker2_plus_0.8.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = 0.8 mm
-version = 2
+version = 3
definition = ultimaker2_plus
[metadata]
diff --git a/resources/variants/ultimaker3_aa0.25.inst.cfg b/resources/variants/ultimaker3_aa0.25.inst.cfg
index 8f06e73f91..447bf0e49c 100644
--- a/resources/variants/ultimaker3_aa0.25.inst.cfg
+++ b/resources/variants/ultimaker3_aa0.25.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = AA 0.25
-version = 2
+version = 3
definition = ultimaker3
[metadata]
diff --git a/resources/variants/ultimaker3_aa0.8.inst.cfg b/resources/variants/ultimaker3_aa0.8.inst.cfg
index 218c1ea3bf..97ee3587cf 100644
--- a/resources/variants/ultimaker3_aa0.8.inst.cfg
+++ b/resources/variants/ultimaker3_aa0.8.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = AA 0.8
-version = 2
+version = 3
definition = ultimaker3
[metadata]
@@ -45,8 +45,8 @@ retraction_amount = 6.5
retraction_count_max = 25
retraction_extrusion_window = 1
retraction_hop = 2
-retraction_hop_enabled = True
retraction_hop_only_when_collides = True
+retraction_min_travel = =line_width * 2
skin_overlap = 5
speed_equalize_flow_enabled = True
speed_layer_0 = 20
diff --git a/resources/variants/ultimaker3_aa04.inst.cfg b/resources/variants/ultimaker3_aa04.inst.cfg
index 2964ab74a0..0e0187e4df 100644
--- a/resources/variants/ultimaker3_aa04.inst.cfg
+++ b/resources/variants/ultimaker3_aa04.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = AA 0.4
-version = 2
+version = 3
definition = ultimaker3
[metadata]
@@ -12,6 +12,7 @@ hardware_type = nozzle
brim_width = 7
machine_nozzle_cool_down_speed = 0.9
machine_nozzle_id = AA 0.4
+machine_nozzle_tip_outer_diameter = 1.0
raft_acceleration = =acceleration_print
raft_airgap = 0.3
raft_base_thickness = =resolveOrValue('layer_height_0') * 1.2
diff --git a/resources/variants/ultimaker3_bb0.8.inst.cfg b/resources/variants/ultimaker3_bb0.8.inst.cfg
index 03b38f225c..f80fa05d1d 100644
--- a/resources/variants/ultimaker3_bb0.8.inst.cfg
+++ b/resources/variants/ultimaker3_bb0.8.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = BB 0.8
-version = 2
+version = 3
definition = ultimaker3
[metadata]
@@ -56,9 +56,8 @@ retraction_amount = 4.5
retraction_count_max = 15
retraction_extrusion_window = =retraction_amount
retraction_hop = 2
-retraction_hop_enabled = True
retraction_hop_only_when_collides = True
-retraction_min_travel = 5
+retraction_min_travel = =line_width * 3
retraction_prime_speed = 15
skin_overlap = 5
speed_layer_0 = 20
diff --git a/resources/variants/ultimaker3_bb04.inst.cfg b/resources/variants/ultimaker3_bb04.inst.cfg
index 78d201318d..91e70f9f98 100644
--- a/resources/variants/ultimaker3_bb04.inst.cfg
+++ b/resources/variants/ultimaker3_bb04.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = BB 0.4
-version = 2
+version = 3
definition = ultimaker3
[metadata]
@@ -20,6 +20,7 @@ jerk_support_interface = =math.ceil(jerk_support * 10 / 15)
jerk_support_bottom = =math.ceil(jerk_support_interface * 1 / 10)
machine_nozzle_heat_up_speed = 1.5
machine_nozzle_id = BB 0.4
+machine_nozzle_tip_outer_diameter = 1.0
prime_tower_purge_volume = 1
raft_base_speed = 20
raft_interface_speed = 20
diff --git a/resources/variants/ultimaker3_extended_aa0.25.inst.cfg b/resources/variants/ultimaker3_extended_aa0.25.inst.cfg
index f45d90b762..b06ec0830b 100644
--- a/resources/variants/ultimaker3_extended_aa0.25.inst.cfg
+++ b/resources/variants/ultimaker3_extended_aa0.25.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = AA 0.25
-version = 2
+version = 3
definition = ultimaker3_extended
[metadata]
diff --git a/resources/variants/ultimaker3_extended_aa0.8.inst.cfg b/resources/variants/ultimaker3_extended_aa0.8.inst.cfg
index 1f5709e504..149b832514 100644
--- a/resources/variants/ultimaker3_extended_aa0.8.inst.cfg
+++ b/resources/variants/ultimaker3_extended_aa0.8.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = AA 0.8
-version = 2
+version = 3
definition = ultimaker3_extended
[metadata]
@@ -45,8 +45,8 @@ retraction_amount = 6.5
retraction_count_max = 25
retraction_extrusion_window = 1
retraction_hop = 2
-retraction_hop_enabled = True
retraction_hop_only_when_collides = True
+retraction_min_travel = =line_width * 2
skin_overlap = 5
speed_equalize_flow_enabled = True
speed_layer_0 = 20
diff --git a/resources/variants/ultimaker3_extended_aa04.inst.cfg b/resources/variants/ultimaker3_extended_aa04.inst.cfg
index 4d9d2b4646..8a2f061224 100644
--- a/resources/variants/ultimaker3_extended_aa04.inst.cfg
+++ b/resources/variants/ultimaker3_extended_aa04.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = AA 0.4
-version = 2
+version = 3
definition = ultimaker3_extended
[metadata]
@@ -12,6 +12,7 @@ hardware_type = nozzle
brim_width = 7
machine_nozzle_cool_down_speed = 0.9
machine_nozzle_id = AA 0.4
+machine_nozzle_tip_outer_diameter = 1.0
raft_acceleration = =acceleration_print
raft_airgap = 0.3
raft_base_thickness = =resolveOrValue('layer_height_0') * 1.2
diff --git a/resources/variants/ultimaker3_extended_bb0.8.inst.cfg b/resources/variants/ultimaker3_extended_bb0.8.inst.cfg
index 752083f42a..541034f119 100644
--- a/resources/variants/ultimaker3_extended_bb0.8.inst.cfg
+++ b/resources/variants/ultimaker3_extended_bb0.8.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = BB 0.8
-version = 2
+version = 3
definition = ultimaker3_extended
[metadata]
@@ -56,9 +56,8 @@ retraction_amount = 4.5
retraction_count_max = 15
retraction_extrusion_window = =retraction_amount
retraction_hop = 2
-retraction_hop_enabled = True
retraction_hop_only_when_collides = True
-retraction_min_travel = 5
+retraction_min_travel = =line_width * 3
retraction_prime_speed = 15
skin_overlap = 5
speed_layer_0 = 20
diff --git a/resources/variants/ultimaker3_extended_bb04.inst.cfg b/resources/variants/ultimaker3_extended_bb04.inst.cfg
index 1ceaf58c5b..5b35351312 100644
--- a/resources/variants/ultimaker3_extended_bb04.inst.cfg
+++ b/resources/variants/ultimaker3_extended_bb04.inst.cfg
@@ -1,6 +1,6 @@
[general]
name = BB 0.4
-version = 2
+version = 3
definition = ultimaker3_extended
[metadata]
@@ -20,6 +20,7 @@ jerk_support_interface = =math.ceil(jerk_support * 10 / 15)
jerk_support_bottom = =math.ceil(jerk_support_interface * 1 / 10)
machine_nozzle_heat_up_speed = 1.5
machine_nozzle_id = BB 0.4
+machine_nozzle_tip_outer_diameter = 1.0
prime_tower_purge_volume = 1
raft_base_speed = 20
raft_interface_speed = 20
diff --git a/run_in_docker.sh b/run_in_docker.sh
new file mode 100644
index 0000000000..eb364fd887
--- /dev/null
+++ b/run_in_docker.sh
@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+Xvfb :1 -screen 0 1280x800x16 &
+export DISPLAY=:1.0
+python3 cura_app.py --headless
\ No newline at end of file
diff --git a/tests/TestProfileRequirements.py b/tests/TestProfileRequirements.py
index a91a08172c..edeec909f2 100644
--- a/tests/TestProfileRequirements.py
+++ b/tests/TestProfileRequirements.py
@@ -22,4 +22,4 @@ def test_ultimaker3extended_variants(um3_file, um3e_file):
um3.read_file(open(os.path.join(directory, um3_file)))
um3e = configparser.ConfigParser()
um3e.read_file(open(os.path.join(directory, um3e_file)))
- assert um3["values"] == um3e["values"]
\ No newline at end of file
+ assert um3["values"] == um3e["values"]
diff --git a/tools/check_preset_settings.py b/tools/check_preset_settings.py
old mode 100755
new mode 100644