From 7f09859be28be7dd5a51d3448dd3d9cbe8d28c14 Mon Sep 17 00:00:00 2001 From: Vlad Gribinchuk Date: Fri, 28 Sep 2018 13:30:48 +0300 Subject: [PATCH 01/30] Settings for the "Minimum Support Area" and "Minimum Support Interface Area" features --- resources/definitions/fdmprinter.def.json | 58 +++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 823635a62a..cd489126bf 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -4083,6 +4083,20 @@ "limit_to_extruder": "support_infill_extruder_nr", "settable_per_mesh": false }, + "minimum_support_area": + { + "label": "Minimum Support Area", + "description": "Minimum area size for support polygons. Polygons which area is smaller than this value will not be generated.", + "unit": "mm²", + "type": "float", + "default_value": 0.0, + "minimum_value": "0", + "enabled": "support_enable", + "limit_to_extruder": "support_infill_extruder_nr", + "settable_per_mesh": true, + "fabricate_enabled": true, + "intermediate_enabled": true + }, "support_interface_enable": { "label": "Enable Support Interface", @@ -4322,6 +4336,50 @@ } } }, + "minimum_interface_area": + { + "label": "Minimum Support Interface Area", + "description": "Minimum area size for support interface polygons. Polygons which area are smaller than this value will not be generated.", + "unit": "mm²", + "type": "float", + "default_value": 1.0, + "minimum_value": "0", + "minimum_value_warning": "minimum_support_area", + "limit_to_extruder": "support_interface_extruder_nr", + "enabled": "support_interface_enable and (support_enable or support_tree_enable)", + "settable_per_mesh": true, + "children": + { + "minimum_roof_area": + { + "label": "Minimum Support Roof Area", + "description": "Minimum area size for the roofs of the support. Polygons which area are smaller than this value will not be generated.", + "unit": "mm²", + "type": "float", + "default_value": 1.0, + "value": "extruderValue(support_roof_extruder_nr, 'minimum_interface_area')", + "minimum_value": "0", + "minimum_value_warning": "minimum_support_area", + "limit_to_extruder": "support_roof_extruder_nr", + "enabled": "support_roof_enable and (support_enable or support_tree_enable)", + "settable_per_mesh": true + }, + "minimum_bottom_area": + { + "label": "Minimum Support Floor Area", + "description": "Minimum area size for the floors of the support. Polygons which area are smaller than this value will not be generated.", + "unit": "mm²", + "type": "float", + "default_value": 1.0, + "value": "extruderValue(support_bottom_extruder_nr, 'minimum_interface_area')", + "minimum_value": "0", + "minimum_value_warning": "minimum_support_area", + "limit_to_extruder": "support_bottom_extruder_nr", + "enabled": "support_bottom_enable and (support_enable or support_tree_enable)", + "settable_per_mesh": true + } + } + }, "support_fan_enable": { "label": "Fan Speed Override", From 44954c4cadf43cee719c03157f3edd5fff397abb Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 1 Nov 2018 14:15:16 +0100 Subject: [PATCH 02/30] Fixes required for semantic versioning CURA-5840 --- plugins/Toolbox/src/Toolbox.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 25e7656999..7db4e8bde0 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -209,11 +209,11 @@ class Toolbox(QObject, Extension): # Get the packages version depending on Cura version settings. def _getSDKVersion(self) -> Union[int, str]: if not hasattr(cura, "CuraVersion"): - return self._plugin_registry.APIVersion + return self._plugin_registry.APIVersion.getMajor() if not hasattr(cura.CuraVersion, "CuraSDKVersion"): # type: ignore - return self._plugin_registry.APIVersion + return self._plugin_registry.APIVersion.getMajor() if not cura.CuraVersion.CuraSDKVersion: # type: ignore - return self._plugin_registry.APIVersion + return self._plugin_registry.APIVersion.getMajor() return cura.CuraVersion.CuraSDKVersion # type: ignore @pyqtSlot() From 75f2f405344e16d0bc191d40c488cc3cea3888e8 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 8 Nov 2018 12:04:41 +0100 Subject: [PATCH 03/30] Make API version configuration for Application CURA-5840 --- cura/API/Interface/__init__.py | 4 ---- cura/API/__init__.py | 4 +--- cura/CuraApplication.py | 3 ++- plugins/Toolbox/src/Toolbox.py | 6 +++--- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/cura/API/Interface/__init__.py b/cura/API/Interface/__init__.py index 742254a1a4..cec174bf0a 100644 --- a/cura/API/Interface/__init__.py +++ b/cura/API/Interface/__init__.py @@ -3,7 +3,6 @@ from typing import TYPE_CHECKING -from UM.PluginRegistry import PluginRegistry from cura.API.Interface.Settings import Settings if TYPE_CHECKING: @@ -23,9 +22,6 @@ if TYPE_CHECKING: class Interface: - # For now we use the same API version to be consistent. - VERSION = PluginRegistry.APIVersion - def __init__(self, application: "CuraApplication") -> None: # API methods specific to the settings portion of the UI self.settings = Settings(application) diff --git a/cura/API/__init__.py b/cura/API/__init__.py index ad07452c1a..b3e702263a 100644 --- a/cura/API/__init__.py +++ b/cura/API/__init__.py @@ -4,7 +4,6 @@ from typing import Optional, TYPE_CHECKING from PyQt5.QtCore import QObject, pyqtProperty -from UM.PluginRegistry import PluginRegistry from cura.API.Backups import Backups from cura.API.Interface import Interface from cura.API.Account import Account @@ -22,7 +21,6 @@ if TYPE_CHECKING: class CuraAPI(QObject): # For now we use the same API version to be consistent. - VERSION = PluginRegistry.APIVersion __instance = None # type: "CuraAPI" _application = None # type: CuraApplication @@ -62,4 +60,4 @@ class CuraAPI(QObject): @property def interface(self) -> "Interface": - return self._interface \ No newline at end of file + return self._interface diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index fc9ba1ebf4..7f0e742a9f 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -133,7 +133,7 @@ except ImportError: CuraVersion = "master" # [CodeStyle: Reflecting imported value] CuraBuildType = "" CuraDebugMode = False - CuraSDKVersion = "" + CuraSDKVersion = "5.0.0" class CuraApplication(QtApplication): @@ -162,6 +162,7 @@ class CuraApplication(QtApplication): def __init__(self, *args, **kwargs): super().__init__(name = "cura", version = CuraVersion, + api_version = CuraSDKVersion, buildtype = CuraBuildType, is_debug_mode = CuraDebugMode, tray_icon_name = "cura-icon-32.png", diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 7db4e8bde0..2d9299fb5a 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -209,11 +209,11 @@ class Toolbox(QObject, Extension): # Get the packages version depending on Cura version settings. def _getSDKVersion(self) -> Union[int, str]: if not hasattr(cura, "CuraVersion"): - return self._plugin_registry.APIVersion.getMajor() + return self._application.getAPIVersion().getMajor() if not hasattr(cura.CuraVersion, "CuraSDKVersion"): # type: ignore - return self._plugin_registry.APIVersion.getMajor() + return self._application.getAPIVersion().getMajor() if not cura.CuraVersion.CuraSDKVersion: # type: ignore - return self._plugin_registry.APIVersion.getMajor() + return self._application.getAPIVersion().getMajor() return cura.CuraVersion.CuraSDKVersion # type: ignore @pyqtSlot() From 7f54cacd0ffb13e07201ac6010457dc62d11c70f Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 9 Nov 2018 14:11:38 +0100 Subject: [PATCH 04/30] Code style Discovered during work on CURA-5840. --- plugins/Toolbox/src/PackagesModel.py | 4 ++-- plugins/Toolbox/src/Toolbox.py | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/plugins/Toolbox/src/PackagesModel.py b/plugins/Toolbox/src/PackagesModel.py index aa5626b7f2..a31facf75a 100644 --- a/plugins/Toolbox/src/PackagesModel.py +++ b/plugins/Toolbox/src/PackagesModel.py @@ -12,7 +12,7 @@ from UM.Qt.ListModel import ListModel from .ConfigsModel import ConfigsModel -## Model that holds cura packages. By setting the filter property the instances held by this model can be changed. +## Model that holds Cura packages. By setting the filter property the instances held by this model can be changed. class PackagesModel(ListModel): def __init__(self, parent = None): super().__init__(parent) @@ -70,7 +70,7 @@ class PackagesModel(ListModel): # Links is a list of dictionaries with "title" and "url". Convert this list into a dict so it's easier # to process. - link_list = package['data']['links'] if 'links' in package['data'] else [] + link_list = package["data"]["links"] if "links" in package["data"] else [] links_dict = {d["title"]: d["url"] for d in link_list} if "author_id" not in package["author"] or "display_name" not in package["author"]: diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 2d9299fb5a..cec7fae6bd 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -172,18 +172,18 @@ class Toolbox(QObject, Extension): self._cloud_api_version = self._getCloudAPIVersion() self._cloud_api_root = self._getCloudAPIRoot() self._api_url = "{cloud_api_root}/cura-packages/v{cloud_api_version}/cura/v{sdk_version}".format( - cloud_api_root=self._cloud_api_root, - cloud_api_version=self._cloud_api_version, - sdk_version=self._sdk_version + cloud_api_root = self._cloud_api_root, + cloud_api_version = self._cloud_api_version, + sdk_version = self._sdk_version ) self._request_urls = { - "authors": QUrl("{base_url}/authors".format(base_url=self._api_url)), - "packages": QUrl("{base_url}/packages".format(base_url=self._api_url)), - "plugins_showcase": QUrl("{base_url}/showcase".format(base_url=self._api_url)), - "plugins_available": QUrl("{base_url}/packages?package_type=plugin".format(base_url=self._api_url)), - "materials_showcase": QUrl("{base_url}/showcase".format(base_url=self._api_url)), - "materials_available": QUrl("{base_url}/packages?package_type=material".format(base_url=self._api_url)), - "materials_generic": QUrl("{base_url}/packages?package_type=material&tags=generic".format(base_url=self._api_url)) + "authors": QUrl("{base_url}/authors".format(base_url = self._api_url)), + "packages": QUrl("{base_url}/packages".format(base_url = self._api_url)), + "plugins_showcase": QUrl("{base_url}/showcase".format(base_url = self._api_url)), + "plugins_available": QUrl("{base_url}/packages?package_type=plugin".format(base_url = self._api_url)), + "materials_showcase": QUrl("{base_url}/showcase".format(base_url = self._api_url)), + "materials_available": QUrl("{base_url}/packages?package_type=material".format(base_url = self._api_url)), + "materials_generic": QUrl("{base_url}/packages?package_type=material&tags=generic".format(base_url = self._api_url)) } # Get the API root for the packages API depending on Cura version settings. @@ -295,7 +295,7 @@ class Toolbox(QObject, Extension): for plugin_id in old_plugin_ids: # Neither the installed packages nor the packages that are scheduled to remove are old plugins if plugin_id not in installed_package_ids and plugin_id not in scheduled_to_remove_package_ids: - Logger.log('i', 'Found a plugin that was installed with the old plugin browser: %s', plugin_id) + Logger.log("i", "Found a plugin that was installed with the old plugin browser: %s", plugin_id) old_metadata = self._plugin_registry.getMetaData(plugin_id) new_metadata = self._convertPluginMetadata(old_metadata) From 9c555bf67f1964fcc0055c7826f1097199548dc8 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 12 Nov 2018 11:02:43 +0100 Subject: [PATCH 05/30] Add typing and always add error message if loading failed There were some places where it would return None. Then in the QML it would give a QML error that the null object has no dictionary items. Contributes to issue CURA-5929. --- cura/Settings/ContainerManager.py | 8 ++++---- cura/Settings/CuraContainerRegistry.py | 15 +++++++-------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index 3cfca1a944..3c1e09f086 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -419,13 +419,13 @@ class ContainerManager(QObject): self._container_name_filters[name_filter] = entry ## Import single profile, file_url does not have to end with curaprofile - @pyqtSlot(QUrl, result="QVariantMap") - def importProfile(self, file_url: QUrl): + @pyqtSlot(QUrl, result = "QVariantMap") + def importProfile(self, file_url: QUrl) -> Dict[str, str]: if not file_url.isValid(): - return + return {"status": "error", "message": catalog.i18nc("@info:status", "Invalid file URL:") + " " + file_url} path = file_url.toLocalFile() if not path: - return + return {"status": "error", "message": catalog.i18nc("@info:status", "Invalid file URL:") + " " + file_url} return self._container_registry.importProfile(path) @pyqtSlot(QObject, QUrl, str) diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index 11640adc0f..63277ebce5 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -5,8 +5,7 @@ import os import re import configparser -from typing import cast, Optional - +from typing import cast, Dict, Optional from PyQt5.QtWidgets import QMessageBox from UM.Decorators import override @@ -161,20 +160,20 @@ class CuraContainerRegistry(ContainerRegistry): ## Imports a profile from a file # - # \param file_name \type{str} the full path and filename of the profile to import - # \return \type{Dict} dict with a 'status' key containing the string 'ok' or 'error', and a 'message' key - # containing a message for the user - def importProfile(self, file_name): + # \param file_name The full path and filename of the profile to import. + # \return Dict with a 'status' key containing the string 'ok' or 'error', + # and a 'message' key containing a message for the user. + def importProfile(self, file_name: str) -> Dict[str, str]: Logger.log("d", "Attempting to import profile %s", file_name) if not file_name: - return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags or !", "Failed to import profile from {0}: {1}", file_name, "Invalid path")} + return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags !", "Failed to import profile from {0}: {1}", file_name, "Invalid path")} plugin_registry = PluginRegistry.getInstance() extension = file_name.split(".")[-1] global_stack = Application.getInstance().getGlobalContainerStack() if not global_stack: - return + return {"status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags !", "Can't import profile from {0} before a printer is added.", file_name)} machine_extruders = [] for position in sorted(global_stack.extruders): From 9d94b0d63eaf1db2b7e08a7ce7f4ae848df98f9e Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 12 Nov 2018 11:16:33 +0100 Subject: [PATCH 06/30] Rename input vs. output parsers Technically you could re-use the variable name but that is confusing. Contributes to issue CURA-5929. --- .../LegacyProfileReader.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/plugins/LegacyProfileReader/LegacyProfileReader.py b/plugins/LegacyProfileReader/LegacyProfileReader.py index cd577218d5..b5b2379b22 100644 --- a/plugins/LegacyProfileReader/LegacyProfileReader.py +++ b/plugins/LegacyProfileReader/LegacyProfileReader.py @@ -82,9 +82,9 @@ class LegacyProfileReader(ProfileReader): profile_id = container_registry.uniqueName("Imported Legacy Profile") profile = InstanceContainer(profile_id) # Create an empty profile. - parser = configparser.ConfigParser(interpolation = None) + input_parser = configparser.ConfigParser(interpolation = None) try: - parser.read([file_name]) # Parse the INI file. + input_parser.read([file_name]) # Parse the INI file. except Exception as e: Logger.log("e", "Unable to open legacy profile %s: %s", file_name, str(e)) return None @@ -92,7 +92,7 @@ class LegacyProfileReader(ProfileReader): # Legacy Cura saved the profile under the section "profile_N" where N is the ID of a machine, except when you export in which case it saves it in the section "profile". # Since importing multiple machine profiles is out of scope, just import the first section we find. section = "" - for found_section in parser.sections(): + for found_section in input_parser.sections(): if found_section.startswith("profile"): section = found_section break @@ -110,7 +110,7 @@ class LegacyProfileReader(ProfileReader): return None defaults = self.prepareDefaults(dict_of_doom) - legacy_settings = self.prepareLocals(parser, section, defaults) #Gets the settings from the legacy profile. + legacy_settings = self.prepareLocals(input_parser, section, defaults) #Gets the settings from the legacy profile. #Check the target version in the Dictionary of Doom with this application version. if "target_version" not in dict_of_doom: @@ -152,15 +152,15 @@ class LegacyProfileReader(ProfileReader): profile.setDirty(True) #Serialise and deserialise in order to perform the version upgrade. - parser = configparser.ConfigParser(interpolation = None) + output_parser = configparser.ConfigParser(interpolation = None) data = profile.serialize() - parser.read_string(data) - parser["general"]["version"] = "1" - if parser.has_section("values"): - parser["settings"] = parser["values"] - del parser["values"] + output_parser.read_string(data) + output_parser["general"]["version"] = "1" + if output_parser.has_section("values"): + output_parser["settings"] = output_parser["values"] + del output_parser["values"] stream = io.StringIO() - parser.write(stream) + output_parser.write(stream) data = stream.getvalue() profile.deserialize(data) From 6ad682b00db9e614ff40eb530133247744e02658 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 12 Nov 2018 12:02:49 +0100 Subject: [PATCH 07/30] Don't serialise legacy profile via Profile instance That profile instance was being explicitly set to version 1 (but then serialised as version 4) and then deserialised with upgrade, so the upgrade was thinking it was upgrading from version 1 to 4, but it was actually upgrading a file which was already at version 4. We shouldn't use the Profile() instance at all but just perform the upgrade on simple string data generated by the configparser. This also updates the format to the newest version (since that was easiest for me to reimplement) but we don't need to ever update this again because it gets passed through the version upgrade system, which upgrades it from version 4000005 to the latest version in the future. Contributes to issue CURA-5929. --- .../LegacyProfileReader.py | 50 ++++++++----------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/plugins/LegacyProfileReader/LegacyProfileReader.py b/plugins/LegacyProfileReader/LegacyProfileReader.py index b5b2379b22..b915242d55 100644 --- a/plugins/LegacyProfileReader/LegacyProfileReader.py +++ b/plugins/LegacyProfileReader/LegacyProfileReader.py @@ -80,7 +80,6 @@ class LegacyProfileReader(ProfileReader): Logger.log("i", "Importing legacy profile from file " + file_name + ".") container_registry = ContainerRegistry.getInstance() profile_id = container_registry.uniqueName("Imported Legacy Profile") - profile = InstanceContainer(profile_id) # Create an empty profile. input_parser = configparser.ConfigParser(interpolation = None) try: @@ -112,13 +111,11 @@ class LegacyProfileReader(ProfileReader): defaults = self.prepareDefaults(dict_of_doom) legacy_settings = self.prepareLocals(input_parser, section, defaults) #Gets the settings from the legacy profile. - #Check the target version in the Dictionary of Doom with this application version. - if "target_version" not in dict_of_doom: - Logger.log("e", "Dictionary of Doom has no target version. Is it the correct JSON file?") - return None - if InstanceContainer.Version != dict_of_doom["target_version"]: - Logger.log("e", "Dictionary of Doom of legacy profile reader (version %s) is not in sync with the current instance container version (version %s)!", dict_of_doom["target_version"], str(InstanceContainer.Version)) - return None + # Serialised format into version 4.5. Do NOT upgrade this, let the version upgrader handle it. + output_parser = configparser.ConfigParser(interpolation = None) + output_parser.add_section("general") + output_parser.add_section("metadata") + output_parser.add_section("values") if "translation" not in dict_of_doom: Logger.log("e", "Dictionary of Doom has no translation. Is it the correct JSON file?") @@ -127,7 +124,7 @@ class LegacyProfileReader(ProfileReader): quality_definition = current_printer_definition.getMetaDataEntry("quality_definition") if not quality_definition: quality_definition = current_printer_definition.getId() - profile.setDefinition(quality_definition) + output_parser["general"]["definition"] = quality_definition for new_setting in dict_of_doom["translation"]: # Evaluate all new settings that would get a value from the translations. old_setting_expression = dict_of_doom["translation"][new_setting] compiled = compile(old_setting_expression, new_setting, "eval") @@ -140,37 +137,34 @@ class LegacyProfileReader(ProfileReader): definitions = current_printer_definition.findDefinitions(key = new_setting) if definitions: if new_value != value_using_defaults and definitions[0].default_value != new_value: # Not equal to the default in the new Cura OR the default in the legacy Cura. - profile.setProperty(new_setting, "value", new_value) # Store the setting in the profile! + output_parser["values"][new_setting] = new_value # Store the setting in the profile! - if len(profile.getAllKeys()) == 0: + if len(output_parser["values"]) == 0: Logger.log("i", "A legacy profile was imported but everything evaluates to the defaults, creating an empty profile.") - profile.setMetaDataEntry("type", "profile") - # don't know what quality_type it is based on, so use "normal" by default - profile.setMetaDataEntry("quality_type", "normal") - profile.setName(profile_id) - profile.setDirty(True) + output_parser["general"]["version"] = "4" + output_parser["general"]["name"] = profile_id + output_parser["metadata"]["type"] = "quality_changes" + output_parser["metadata"]["quality_type"] = "normal" # Don't know what quality_type it is based on, so use "normal" by default. + output_parser["metadata"]["position"] = "0" # We only support single extrusion. + output_parser["metadata"]["setting_version"] = "5" # What the dictionary of doom is made for. - #Serialise and deserialise in order to perform the version upgrade. - output_parser = configparser.ConfigParser(interpolation = None) - data = profile.serialize() - output_parser.read_string(data) - output_parser["general"]["version"] = "1" - if output_parser.has_section("values"): - output_parser["settings"] = output_parser["values"] - del output_parser["values"] + # Serialise in order to perform the version upgrade. stream = io.StringIO() output_parser.write(stream) data = stream.getvalue() - profile.deserialize(data) - # The definition can get reset to fdmprinter during the deserialization's upgrade. Here we set the definition - # again. - profile.setDefinition(quality_definition) + profile = InstanceContainer(profile_id) + profile.deserialize(data) # Also performs the version upgrade. + profile.setDirty(True) #We need to return one extruder stack and one global stack. global_container_id = container_registry.uniqueName("Global Imported Legacy Profile") + # We duplicate the extruder profile into the global stack. + # This may introduce some settings that are global in the extruder stack and some settings that are per-extruder in the global stack. + # We don't care about that. The engine will ignore them anyway. global_profile = profile.duplicate(new_id = global_container_id, new_name = profile_id) #Needs to have the same name as the extruder profile. + del global_profile.getMetaData()["position"] # Has no position because it's global. global_profile.setDirty(True) profile_definition = "fdmprinter" From 6022ed0f2347001efb013cdbad7ba952fb532ab4 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 12 Nov 2018 12:06:00 +0100 Subject: [PATCH 08/30] Update target version in DoD There are no changes to these settings, luckily. This target version is now only here for documentation (like source_version was). It is no longer actually used by the code. Contributes to issue CURA-5929. --- plugins/LegacyProfileReader/DictionaryOfDoom.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/LegacyProfileReader/DictionaryOfDoom.json b/plugins/LegacyProfileReader/DictionaryOfDoom.json index 0be413dd2c..f65cc271d1 100644 --- a/plugins/LegacyProfileReader/DictionaryOfDoom.json +++ b/plugins/LegacyProfileReader/DictionaryOfDoom.json @@ -1,6 +1,6 @@ { "source_version": "15.04", - "target_version": 3, + "target_version": "4.5", "translation": { "machine_nozzle_size": "nozzle_size", From 27aff4e5da270f446e03545f69247f7e38abc3d3 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 12 Nov 2018 12:48:49 +0100 Subject: [PATCH 09/30] Fix typing issues Because this function now has typing, it's raising a load of issues with it. Contributes to issue CURA-5929. --- cura/Machines/QualityManager.py | 4 ++-- cura/Settings/CuraContainerRegistry.py | 24 ++++++++++--------- .../LegacyProfileReader.py | 2 +- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/cura/Machines/QualityManager.py b/cura/Machines/QualityManager.py index fc8262de52..a784d17f0b 100644 --- a/cura/Machines/QualityManager.py +++ b/cura/Machines/QualityManager.py @@ -16,7 +16,7 @@ from .QualityGroup import QualityGroup from .QualityNode import QualityNode if TYPE_CHECKING: - from UM.Settings.DefinitionContainer import DefinitionContainer + from UM.Settings.Interfaces import DefinitionContainerInterface from cura.Settings.GlobalStack import GlobalStack from .QualityChangesGroup import QualityChangesGroup from cura.CuraApplication import CuraApplication @@ -538,7 +538,7 @@ 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_definition: "DefinitionContainer", +def getMachineDefinitionIDForQualitySearch(machine_definition: "DefinitionContainerInterface", default_definition_id: str = "fdmprinter") -> str: machine_definition_id = default_definition_id if parseBool(machine_definition.getMetaDataEntry("has_machine_quality", False)): diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index 63277ebce5..9f44d075e0 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -10,6 +10,7 @@ from PyQt5.QtWidgets import QMessageBox from UM.Decorators import override from UM.Settings.ContainerFormatError import ContainerFormatError +from UM.Settings.Interfaces import ContainerInterface from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.ContainerStack import ContainerStack from UM.Settings.InstanceContainer import InstanceContainer @@ -27,7 +28,7 @@ from . import GlobalStack import cura.CuraApplication from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch -from cura.ReaderWriters.ProfileReader import NoProfileException +from cura.ReaderWriters.ProfileReader import NoProfileException, ProfileReader from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") @@ -182,7 +183,7 @@ class CuraContainerRegistry(ContainerRegistry): for plugin_id, meta_data in self._getIOPlugins("profile_reader"): if meta_data["profile_reader"][0]["extension"] != extension: continue - profile_reader = plugin_registry.getPluginObject(plugin_id) + profile_reader = cast(ProfileReader, 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: @@ -220,13 +221,13 @@ class CuraContainerRegistry(ContainerRegistry): # 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: + machine_definitions = self.findDefinitionContainers(id = profile_definition) + if not machine_definitions: 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 !", "This profile {0} contains incorrect data, could not import it.", file_name) } - machine_definition = machine_definition[0] + machine_definition = machine_definitions[0] # Get the expected machine definition. # i.e.: We expect gcode for a UM2 Extended to be defined as normal UM2 gcode... @@ -273,11 +274,12 @@ class CuraContainerRegistry(ContainerRegistry): setting_value = global_profile.getProperty(qc_setting_key, "value") 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. - profile.addInstance(new_instance) - profile.setDirty(True) + if setting_definition is not None: + 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. + profile.addInstance(new_instance) + profile.setDirty(True) global_profile.removeInstance(qc_setting_key, postpone_emit=True) extruder_profiles.append(profile) @@ -289,7 +291,7 @@ 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_stack.getBottom().getId() + "_" + name_seed).lower().replace(" ", "_") + profile_id = (cast(ContainerInterface, global_stack.getBottom()).getId() + "_" + name_seed).lower().replace(" ", "_") elif profile_index < len(machine_extruders) + 1: # This is assumed to be an extruder profile diff --git a/plugins/LegacyProfileReader/LegacyProfileReader.py b/plugins/LegacyProfileReader/LegacyProfileReader.py index b915242d55..9a74c15082 100644 --- a/plugins/LegacyProfileReader/LegacyProfileReader.py +++ b/plugins/LegacyProfileReader/LegacyProfileReader.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 configparser # For reading the legacy profile INI files. From 6d1b64465a8142c7d9e52b4d3a48ac4e5438cf96 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 12 Nov 2018 12:50:18 +0100 Subject: [PATCH 10/30] Fix URL in error message Contributes to issue CURA-5929. --- cura/Settings/ContainerManager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index 3c1e09f086..133e04e8fc 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -422,10 +422,10 @@ class ContainerManager(QObject): @pyqtSlot(QUrl, result = "QVariantMap") def importProfile(self, file_url: QUrl) -> Dict[str, str]: if not file_url.isValid(): - return {"status": "error", "message": catalog.i18nc("@info:status", "Invalid file URL:") + " " + file_url} + return {"status": "error", "message": catalog.i18nc("@info:status", "Invalid file URL:") + " " + str(file_url)} path = file_url.toLocalFile() if not path: - return {"status": "error", "message": catalog.i18nc("@info:status", "Invalid file URL:") + " " + file_url} + return {"status": "error", "message": catalog.i18nc("@info:status", "Invalid file URL:") + " " + str(file_url)} return self._container_registry.importProfile(path) @pyqtSlot(QObject, QUrl, str) From bbbb08c793e45b49e0421daf900afdc2bda31a81 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 12 Nov 2018 13:02:28 +0100 Subject: [PATCH 11/30] Add test for prepareDefaults Contributes to issue CURA-5929. --- .../tests/TestLegacyProfileReader.py | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 plugins/LegacyProfileReader/tests/TestLegacyProfileReader.py diff --git a/plugins/LegacyProfileReader/tests/TestLegacyProfileReader.py b/plugins/LegacyProfileReader/tests/TestLegacyProfileReader.py new file mode 100644 index 0000000000..9f6ffe4f24 --- /dev/null +++ b/plugins/LegacyProfileReader/tests/TestLegacyProfileReader.py @@ -0,0 +1,31 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +import pytest #To register tests with. + +from LegacyProfileReader import LegacyProfileReader #The module we're testing. + +@pytest.fixture +def legacy_profile_reader(): + return LegacyProfileReader() + +test_prepareDefaultsData = [ + { + "defaults": { + "foo": "bar" + }, + "cheese": "delicious" + }, + { + "cat": "fluffy", + "dog": "floofy" + } +] + +@pytest.mark.parametrize("input", test_prepareDefaultsData) +def test_prepareDefaults(legacy_profile_reader, input): + output = legacy_profile_reader.prepareDefaults(input) + if "defaults" in input: + assert input["defaults"] == output + else: + assert output == {} \ No newline at end of file From 0bf7bf4cbe5f242c6f520d947064631bb5dd7317 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 12 Nov 2018 13:33:44 +0100 Subject: [PATCH 12/30] Fix handling dictionaries without 'defaults' section According to the test, this should return an empty dict then. Contributes to issue CURA-5929. --- plugins/LegacyProfileReader/LegacyProfileReader.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/LegacyProfileReader/LegacyProfileReader.py b/plugins/LegacyProfileReader/LegacyProfileReader.py index 9a74c15082..7974bbe829 100644 --- a/plugins/LegacyProfileReader/LegacyProfileReader.py +++ b/plugins/LegacyProfileReader/LegacyProfileReader.py @@ -6,6 +6,7 @@ import io import json # For reading the Dictionary of Doom. import math # For mathematical operations included in the Dictionary of Doom. import os.path # For concatenating the path to the plugin and the relative path to the Dictionary of Doom. +from typing import Dict from UM.Application import Application # To get the machine manager to create the new profile in. from UM.Logger import Logger # Logging errors. @@ -33,10 +34,11 @@ class LegacyProfileReader(ProfileReader): # \param json The JSON file to load the default setting values from. This # should not be a URL but a pre-loaded JSON handle. # \return A dictionary of the default values of the legacy Cura version. - def prepareDefaults(self, json): + def prepareDefaults(self, json: Dict[str, Dict[str, str]]) -> Dict[str, str]: defaults = {} - for key in json["defaults"]: # We have to copy over all defaults from the JSON handle to a normal dict. - defaults[key] = json["defaults"][key] + if "defaults" in json: + for key in json["defaults"]: # We have to copy over all defaults from the JSON handle to a normal dict. + defaults[key] = json["defaults"][key] return defaults ## Prepares the local variables that can be used in evaluation of computing From 91e8c177fe2c7b999d7724112c4bcb71d9e8912b Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 12 Nov 2018 13:35:18 +0100 Subject: [PATCH 13/30] Add test for prepareLocal Contributes to issue CURA-5929. --- .../tests/TestLegacyProfileReader.py | 43 +++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/plugins/LegacyProfileReader/tests/TestLegacyProfileReader.py b/plugins/LegacyProfileReader/tests/TestLegacyProfileReader.py index 9f6ffe4f24..f54939e884 100644 --- a/plugins/LegacyProfileReader/tests/TestLegacyProfileReader.py +++ b/plugins/LegacyProfileReader/tests/TestLegacyProfileReader.py @@ -1,9 +1,10 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -import pytest #To register tests with. +import configparser # An input for some functions we're testing. +import pytest # To register tests with. -from LegacyProfileReader import LegacyProfileReader #The module we're testing. +from LegacyProfileReader import LegacyProfileReader # The module we're testing. @pytest.fixture def legacy_profile_reader(): @@ -11,7 +12,8 @@ def legacy_profile_reader(): test_prepareDefaultsData = [ { - "defaults": { + "defaults": + { "foo": "bar" }, "cheese": "delicious" @@ -28,4 +30,37 @@ def test_prepareDefaults(legacy_profile_reader, input): if "defaults" in input: assert input["defaults"] == output else: - assert output == {} \ No newline at end of file + assert output == {} + +test_prepareLocalsData = [ + ( + { # Parser data. + "profile": + { + "layer_height": "0.2", + "infill_density": "30" + } + }, + "profile", # Config section. + { # Defaults. + "layer_height": "0.1", + "infill_density": "20", + "line_width": "0.4" + } + ) +] + +@pytest.mark.parametrize("parser_data, config_section, defaults", test_prepareLocalsData) +def test_prepareLocals(legacy_profile_reader, parser_data, config_section, defaults): + parser = configparser.ConfigParser() + parser.read_dict(parser_data) + + output = legacy_profile_reader.prepareLocals(parser, config_section, defaults) + + assert set(defaults.keys()) <= set(output.keys()) # All defaults must be in there. + assert set(parser_data[config_section]) <= set(output.keys()) # All overwritten values must be in there. + for key in output: + if key in parser_data[config_section]: + assert output[key] == parser_data[config_section][key] # If overwritten, must be the overwritten value. + else: + assert output[key] == defaults[key] # Otherwise must be equal to the default. \ No newline at end of file From 53c9cdc3fe7d0a6c4efa0bd2223edd15e9be71a1 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 12 Nov 2018 13:42:14 +0100 Subject: [PATCH 14/30] Add alternative scenarios for prepareLocals Contributes to issue CURA-5929. --- .../tests/TestLegacyProfileReader.py | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/plugins/LegacyProfileReader/tests/TestLegacyProfileReader.py b/plugins/LegacyProfileReader/tests/TestLegacyProfileReader.py index f54939e884..6d730ed786 100644 --- a/plugins/LegacyProfileReader/tests/TestLegacyProfileReader.py +++ b/plugins/LegacyProfileReader/tests/TestLegacyProfileReader.py @@ -33,7 +33,7 @@ def test_prepareDefaults(legacy_profile_reader, input): assert output == {} test_prepareLocalsData = [ - ( + ( # Ordinary case. { # Parser data. "profile": { @@ -47,6 +47,56 @@ test_prepareLocalsData = [ "infill_density": "20", "line_width": "0.4" } + ), + ( # Empty data. + { # Parser data. + "profile": + { + } + }, + "profile", # Config section. + { # Defaults. + } + ), + ( # All defaults. + { # Parser data. + "profile": + { + } + }, + "profile", # Config section. + { # Defaults. + "foo": "bar", + "boo": "far" + } + ), + ( # Multiple config sections. + { # Parser data. + "some_other_name": + { + "foo": "bar" + }, + "profile": + { + "foo": "baz" #Not the same as in some_other_name + } + }, + "profile", # Config section. + { # Defaults. + "foo": "bla" + } + ), + ( # Section does not exist. + { # Parser data. + "some_other_name": + { + "foo": "bar" + }, + }, + "profile", # Config section. + { # Defaults. + "foo": "baz" + } ) ] From e18ea4bca4d37d2feabcd47e0bf42cf36fedac68 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 12 Nov 2018 13:47:24 +0100 Subject: [PATCH 15/30] Expect a NoSectionError if testing with a section that is missing We want to get that error in order to debug. Contributes to issue CURA-5929. --- .../tests/TestLegacyProfileReader.py | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/plugins/LegacyProfileReader/tests/TestLegacyProfileReader.py b/plugins/LegacyProfileReader/tests/TestLegacyProfileReader.py index 6d730ed786..f20658ae5d 100644 --- a/plugins/LegacyProfileReader/tests/TestLegacyProfileReader.py +++ b/plugins/LegacyProfileReader/tests/TestLegacyProfileReader.py @@ -85,18 +85,6 @@ test_prepareLocalsData = [ { # Defaults. "foo": "bla" } - ), - ( # Section does not exist. - { # Parser data. - "some_other_name": - { - "foo": "bar" - }, - }, - "profile", # Config section. - { # Defaults. - "foo": "baz" - } ) ] @@ -113,4 +101,28 @@ def test_prepareLocals(legacy_profile_reader, parser_data, config_section, defau if key in parser_data[config_section]: assert output[key] == parser_data[config_section][key] # If overwritten, must be the overwritten value. else: - assert output[key] == defaults[key] # Otherwise must be equal to the default. \ No newline at end of file + assert output[key] == defaults[key] # Otherwise must be equal to the default. + +test_prepareLocalsNoSectionErrorData = [ + ( # Section does not exist. + { # Parser data. + "some_other_name": + { + "foo": "bar" + }, + }, + "profile", # Config section. + { # Defaults. + "foo": "baz" + } + ) +] + +## Test cases where a key error is expected. +@pytest.mark.parametrize("parser_data, config_section, defaults", test_prepareLocalsNoSectionErrorData) +def test_prepareLocalsNoSectionError(legacy_profile_reader, parser_data, config_section, defaults): + parser = configparser.ConfigParser() + parser.read_dict(parser_data) + + with pytest.raises(configparser.NoSectionError): + legacy_profile_reader.prepareLocals(parser, config_section, defaults) \ No newline at end of file From 7e87a303cbf54d36c9404f2a1060417ae6dcc8e4 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 12 Nov 2018 13:49:13 +0100 Subject: [PATCH 16/30] Remove config_section parameter Just always take 'profile'. We don't need to test with anything else. Just adjust the data to it. Contributes to issue CURA-5929. --- .../tests/TestLegacyProfileReader.py | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/plugins/LegacyProfileReader/tests/TestLegacyProfileReader.py b/plugins/LegacyProfileReader/tests/TestLegacyProfileReader.py index f20658ae5d..828c14bbf5 100644 --- a/plugins/LegacyProfileReader/tests/TestLegacyProfileReader.py +++ b/plugins/LegacyProfileReader/tests/TestLegacyProfileReader.py @@ -41,7 +41,6 @@ test_prepareLocalsData = [ "infill_density": "30" } }, - "profile", # Config section. { # Defaults. "layer_height": "0.1", "infill_density": "20", @@ -54,7 +53,6 @@ test_prepareLocalsData = [ { } }, - "profile", # Config section. { # Defaults. } ), @@ -64,7 +62,6 @@ test_prepareLocalsData = [ { } }, - "profile", # Config section. { # Defaults. "foo": "bar", "boo": "far" @@ -81,25 +78,24 @@ test_prepareLocalsData = [ "foo": "baz" #Not the same as in some_other_name } }, - "profile", # Config section. { # Defaults. "foo": "bla" } ) ] -@pytest.mark.parametrize("parser_data, config_section, defaults", test_prepareLocalsData) -def test_prepareLocals(legacy_profile_reader, parser_data, config_section, defaults): +@pytest.mark.parametrize("parser_data, defaults", test_prepareLocalsData) +def test_prepareLocals(legacy_profile_reader, parser_data, defaults): parser = configparser.ConfigParser() parser.read_dict(parser_data) - output = legacy_profile_reader.prepareLocals(parser, config_section, defaults) + output = legacy_profile_reader.prepareLocals(parser, "profile", defaults) assert set(defaults.keys()) <= set(output.keys()) # All defaults must be in there. - assert set(parser_data[config_section]) <= set(output.keys()) # All overwritten values must be in there. + assert set(parser_data["profile"]) <= set(output.keys()) # All overwritten values must be in there. for key in output: - if key in parser_data[config_section]: - assert output[key] == parser_data[config_section][key] # If overwritten, must be the overwritten value. + if key in parser_data["profile"]: + assert output[key] == parser_data["profile"][key] # If overwritten, must be the overwritten value. else: assert output[key] == defaults[key] # Otherwise must be equal to the default. @@ -111,7 +107,6 @@ test_prepareLocalsNoSectionErrorData = [ "foo": "bar" }, }, - "profile", # Config section. { # Defaults. "foo": "baz" } @@ -119,10 +114,10 @@ test_prepareLocalsNoSectionErrorData = [ ] ## Test cases where a key error is expected. -@pytest.mark.parametrize("parser_data, config_section, defaults", test_prepareLocalsNoSectionErrorData) -def test_prepareLocalsNoSectionError(legacy_profile_reader, parser_data, config_section, defaults): +@pytest.mark.parametrize("parser_data, defaults", test_prepareLocalsNoSectionErrorData) +def test_prepareLocalsNoSectionError(legacy_profile_reader, parser_data, defaults): parser = configparser.ConfigParser() parser.read_dict(parser_data) with pytest.raises(configparser.NoSectionError): - legacy_profile_reader.prepareLocals(parser, config_section, defaults) \ No newline at end of file + legacy_profile_reader.prepareLocals(parser, "profile", defaults) \ No newline at end of file From 7bd55f5064ae86ceadc0653b6b9f05df1186f2e1 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 12 Nov 2018 16:28:29 +0100 Subject: [PATCH 17/30] Add test for read() function This is complex. I don't want to get into the actual translations from the DoD here. Contributes to issue CURA-5929. --- .../tests/TestLegacyProfileReader.py | 69 ++++++++++++++++++- .../LegacyProfileReader/tests/normal_case.ini | 7 ++ 2 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 plugins/LegacyProfileReader/tests/normal_case.ini diff --git a/plugins/LegacyProfileReader/tests/TestLegacyProfileReader.py b/plugins/LegacyProfileReader/tests/TestLegacyProfileReader.py index 828c14bbf5..480a61f301 100644 --- a/plugins/LegacyProfileReader/tests/TestLegacyProfileReader.py +++ b/plugins/LegacyProfileReader/tests/TestLegacyProfileReader.py @@ -2,8 +2,16 @@ # Cura is released under the terms of the LGPLv3 or higher. import configparser # An input for some functions we're testing. +import os.path # To find the integration test .ini files. import pytest # To register tests with. +import unittest.mock # To mock the application, plug-in and container registry out. +import UM.Application # To mock the application out. +import UM.PluginRegistry # To mock the plug-in registry out. +import UM.Settings.ContainerRegistry # To mock the container registry out. +import UM.Settings.InstanceContainer # To intercept the serialised data from the read() function. + +import LegacyProfileReader as LegacyProfileReaderModule # To get the directory of the module. from LegacyProfileReader import LegacyProfileReader # The module we're testing. @pytest.fixture @@ -120,4 +128,63 @@ def test_prepareLocalsNoSectionError(legacy_profile_reader, parser_data, default parser.read_dict(parser_data) with pytest.raises(configparser.NoSectionError): - legacy_profile_reader.prepareLocals(parser, "profile", defaults) \ No newline at end of file + legacy_profile_reader.prepareLocals(parser, "profile", defaults) + +intercepted_data = "" + +@pytest.mark.parametrize("file_name", ["normal_case.ini"]) +def test_read(legacy_profile_reader, file_name): + # Mock out all dependencies. Quite a lot! + global_stack = unittest.mock.MagicMock() + global_stack.getProperty = unittest.mock.MagicMock(return_value = 1) # For machine_extruder_count setting. + def getMetaDataEntry(key, default_value = ""): + if key == "quality_definition": + return "mocked_quality_definition" + if key == "has_machine_quality": + return "True" + global_stack.definition.getMetaDataEntry = getMetaDataEntry + global_stack.definition.getId = unittest.mock.MagicMock(return_value = "mocked_global_definition") + application = unittest.mock.MagicMock() + application.getGlobalContainerStack = unittest.mock.MagicMock(return_value = global_stack) + application_getInstance = unittest.mock.MagicMock(return_value = application) + container_registry = unittest.mock.MagicMock() + container_registry_getInstance = unittest.mock.MagicMock(return_value = container_registry) + container_registry.uniqueName = unittest.mock.MagicMock(return_value = "Imported Legacy Profile") + container_registry.findDefinitionContainers = unittest.mock.MagicMock(return_value = [global_stack.definition]) + UM.Settings.InstanceContainer.setContainerRegistry(container_registry) + plugin_registry = unittest.mock.MagicMock() + plugin_registry_getInstance = unittest.mock.MagicMock(return_value = plugin_registry) + plugin_registry.getPluginPath = unittest.mock.MagicMock(return_value = os.path.dirname(LegacyProfileReaderModule.__file__)) + + # Mock out the resulting InstanceContainer so that we can intercept the data before it's passed through the version upgrader. + def deserialize(self, data): # Intercepts the serialised data that we'd perform the version upgrade from when deserializing. + global intercepted_data + intercepted_data = data + + parser = configparser.ConfigParser() + parser.read_string(data) + self._metadata["position"] = parser["metadata"]["position"] + def duplicate(self, new_id, new_name): + self._metadata["id"] = new_id + self._metadata["name"] = new_name + return self + + with unittest.mock.patch.object(UM.Application.Application, "getInstance", application_getInstance): + with unittest.mock.patch.object(UM.Settings.ContainerRegistry.ContainerRegistry, "getInstance", container_registry_getInstance): + with unittest.mock.patch.object(UM.PluginRegistry.PluginRegistry, "getInstance", plugin_registry_getInstance): + with unittest.mock.patch.object(UM.Settings.InstanceContainer.InstanceContainer, "deserialize", deserialize): + with unittest.mock.patch.object(UM.Settings.InstanceContainer.InstanceContainer, "duplicate", duplicate): + result = legacy_profile_reader.read(os.path.join(os.path.dirname(__file__), file_name)) + + assert len(result) == 1 + + # Let's see what's inside the actual output file that we generated. + parser = configparser.ConfigParser() + parser.read_string(intercepted_data) + assert parser["general"]["definition"] == "mocked_quality_definition" + assert parser["general"]["version"] == "4" # Yes, before we upgraded. + assert parser["general"]["name"] == "Imported Legacy Profile" # Because we overwrote uniqueName. + assert parser["metadata"]["type"] == "quality_changes" + assert parser["metadata"]["quality_type"] == "normal" + assert parser["metadata"]["position"] == "0" + assert parser["metadata"]["setting_version"] == "5" # Yes, before we upgraded. \ No newline at end of file diff --git a/plugins/LegacyProfileReader/tests/normal_case.ini b/plugins/LegacyProfileReader/tests/normal_case.ini new file mode 100644 index 0000000000..213444d2d3 --- /dev/null +++ b/plugins/LegacyProfileReader/tests/normal_case.ini @@ -0,0 +1,7 @@ +[profile] +foo = bar +boo = far +fill_overlap = 3 + +[alterations] +some = values From 2486a5b5da9dec0c07f5e6326f889ab6c05e91dd Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 13 Nov 2018 11:48:02 +0100 Subject: [PATCH 18/30] Improve generic profiles for fabtotum and zyyx These printers had profiles for zyyx_pro and fabtotum material suites, which were specific to those materials, but caused the generic materials to no longer have any profile. Let's use these machine-specific profiles for the generic materials as well. For the specific materials this changes nothing because the same profiles are still matched. For the generic materials this should improve the quality if the quality profile is tuned in any way to the machine. --- resources/quality/fabtotum/fabtotum_abs_fast.inst.cfg | 2 +- resources/quality/fabtotum/fabtotum_abs_high.inst.cfg | 2 +- resources/quality/fabtotum/fabtotum_abs_normal.inst.cfg | 2 +- resources/quality/fabtotum/fabtotum_nylon_fast.inst.cfg | 2 +- resources/quality/fabtotum/fabtotum_nylon_high.inst.cfg | 2 +- resources/quality/fabtotum/fabtotum_nylon_normal.inst.cfg | 2 +- resources/quality/fabtotum/fabtotum_pla_fast.inst.cfg | 2 +- resources/quality/fabtotum/fabtotum_pla_high.inst.cfg | 2 +- resources/quality/fabtotum/fabtotum_pla_normal.inst.cfg | 2 +- resources/quality/fabtotum/fabtotum_tpu_fast.inst.cfg | 2 +- resources/quality/fabtotum/fabtotum_tpu_high.inst.cfg | 2 +- resources/quality/fabtotum/fabtotum_tpu_normal.inst.cfg | 2 +- resources/quality/zyyx/zyyx_agile_pro_flex_fast.inst.cfg | 2 +- resources/quality/zyyx/zyyx_agile_pro_flex_fine.inst.cfg | 2 +- resources/quality/zyyx/zyyx_agile_pro_flex_normal.inst.cfg | 2 +- resources/quality/zyyx/zyyx_agile_pro_pla_fast.inst.cfg | 2 +- resources/quality/zyyx/zyyx_agile_pro_pla_fine.inst.cfg | 2 +- resources/quality/zyyx/zyyx_agile_pro_pla_normal.inst.cfg | 2 +- 18 files changed, 18 insertions(+), 18 deletions(-) diff --git a/resources/quality/fabtotum/fabtotum_abs_fast.inst.cfg b/resources/quality/fabtotum/fabtotum_abs_fast.inst.cfg index f2628e16df..95e8b93b36 100644 --- a/resources/quality/fabtotum/fabtotum_abs_fast.inst.cfg +++ b/resources/quality/fabtotum/fabtotum_abs_fast.inst.cfg @@ -8,7 +8,7 @@ setting_version = 5 type = quality quality_type = fast weight = -1 -material = fabtotum_abs +material = generic_abs [values] adhesion_type = raft diff --git a/resources/quality/fabtotum/fabtotum_abs_high.inst.cfg b/resources/quality/fabtotum/fabtotum_abs_high.inst.cfg index d750eb98a3..baedf0ed2b 100644 --- a/resources/quality/fabtotum/fabtotum_abs_high.inst.cfg +++ b/resources/quality/fabtotum/fabtotum_abs_high.inst.cfg @@ -8,7 +8,7 @@ setting_version = 5 type = quality quality_type = high weight = 1 -material = fabtotum_abs +material = generic_abs [values] adhesion_type = raft diff --git a/resources/quality/fabtotum/fabtotum_abs_normal.inst.cfg b/resources/quality/fabtotum/fabtotum_abs_normal.inst.cfg index 7db9682c8a..58933486ee 100644 --- a/resources/quality/fabtotum/fabtotum_abs_normal.inst.cfg +++ b/resources/quality/fabtotum/fabtotum_abs_normal.inst.cfg @@ -8,7 +8,7 @@ setting_version = 5 type = quality quality_type = normal weight = 0 -material = fabtotum_abs +material = generic_abs [values] adhesion_type = raft diff --git a/resources/quality/fabtotum/fabtotum_nylon_fast.inst.cfg b/resources/quality/fabtotum/fabtotum_nylon_fast.inst.cfg index b6629d1fac..00f0737227 100644 --- a/resources/quality/fabtotum/fabtotum_nylon_fast.inst.cfg +++ b/resources/quality/fabtotum/fabtotum_nylon_fast.inst.cfg @@ -8,7 +8,7 @@ setting_version = 5 type = quality quality_type = fast weight = -1 -material = fabtotum_nylon +material = generic_nylon [values] adhesion_type = raft diff --git a/resources/quality/fabtotum/fabtotum_nylon_high.inst.cfg b/resources/quality/fabtotum/fabtotum_nylon_high.inst.cfg index f819f2bbd5..bd7f32c9ba 100644 --- a/resources/quality/fabtotum/fabtotum_nylon_high.inst.cfg +++ b/resources/quality/fabtotum/fabtotum_nylon_high.inst.cfg @@ -8,7 +8,7 @@ setting_version = 5 type = quality quality_type = high weight = 1 -material = fabtotum_nylon +material = generic_nylon [values] adhesion_type = raft diff --git a/resources/quality/fabtotum/fabtotum_nylon_normal.inst.cfg b/resources/quality/fabtotum/fabtotum_nylon_normal.inst.cfg index 8e0ed4f93c..6a450e7ffe 100644 --- a/resources/quality/fabtotum/fabtotum_nylon_normal.inst.cfg +++ b/resources/quality/fabtotum/fabtotum_nylon_normal.inst.cfg @@ -8,7 +8,7 @@ setting_version = 5 type = quality quality_type = normal weight = 0 -material = fabtotum_nylon +material = generic_nylon [values] adhesion_type = raft diff --git a/resources/quality/fabtotum/fabtotum_pla_fast.inst.cfg b/resources/quality/fabtotum/fabtotum_pla_fast.inst.cfg index dce262216a..afac0b0884 100644 --- a/resources/quality/fabtotum/fabtotum_pla_fast.inst.cfg +++ b/resources/quality/fabtotum/fabtotum_pla_fast.inst.cfg @@ -8,7 +8,7 @@ setting_version = 5 type = quality quality_type = fast weight = -1 -material = fabtotum_pla +material = generic_pla [values] adhesion_type = skirt diff --git a/resources/quality/fabtotum/fabtotum_pla_high.inst.cfg b/resources/quality/fabtotum/fabtotum_pla_high.inst.cfg index 2dce693235..89dc6d9b33 100644 --- a/resources/quality/fabtotum/fabtotum_pla_high.inst.cfg +++ b/resources/quality/fabtotum/fabtotum_pla_high.inst.cfg @@ -8,7 +8,7 @@ setting_version = 5 type = quality quality_type = high weight = 1 -material = fabtotum_pla +material = generic_pla [values] adhesion_type = skirt diff --git a/resources/quality/fabtotum/fabtotum_pla_normal.inst.cfg b/resources/quality/fabtotum/fabtotum_pla_normal.inst.cfg index 01f730c2e8..e5496a13d4 100644 --- a/resources/quality/fabtotum/fabtotum_pla_normal.inst.cfg +++ b/resources/quality/fabtotum/fabtotum_pla_normal.inst.cfg @@ -8,7 +8,7 @@ setting_version = 5 type = quality quality_type = normal weight = 0 -material = fabtotum_pla +material = generic_pla [values] adhesion_type = skirt diff --git a/resources/quality/fabtotum/fabtotum_tpu_fast.inst.cfg b/resources/quality/fabtotum/fabtotum_tpu_fast.inst.cfg index be8d2e3e3a..7917c92514 100644 --- a/resources/quality/fabtotum/fabtotum_tpu_fast.inst.cfg +++ b/resources/quality/fabtotum/fabtotum_tpu_fast.inst.cfg @@ -6,7 +6,7 @@ name = Fast Quality [metadata] type = quality setting_version = 5 -material = fabtotum_tpu +material = generic_tpu quality_type = fast weight = -1 diff --git a/resources/quality/fabtotum/fabtotum_tpu_high.inst.cfg b/resources/quality/fabtotum/fabtotum_tpu_high.inst.cfg index 2dcaa15bd3..1c31967d79 100644 --- a/resources/quality/fabtotum/fabtotum_tpu_high.inst.cfg +++ b/resources/quality/fabtotum/fabtotum_tpu_high.inst.cfg @@ -6,7 +6,7 @@ name = High Quality [metadata] type = quality setting_version = 5 -material = fabtotum_tpu +material = generic_tpu quality_type = high weight = 1 diff --git a/resources/quality/fabtotum/fabtotum_tpu_normal.inst.cfg b/resources/quality/fabtotum/fabtotum_tpu_normal.inst.cfg index e680038e26..0a3821f953 100644 --- a/resources/quality/fabtotum/fabtotum_tpu_normal.inst.cfg +++ b/resources/quality/fabtotum/fabtotum_tpu_normal.inst.cfg @@ -6,7 +6,7 @@ name = Normal Quality [metadata] type = quality setting_version = 5 -material = fabtotum_TPU +material = generic_tpu quality_type = normal weight = 0 diff --git a/resources/quality/zyyx/zyyx_agile_pro_flex_fast.inst.cfg b/resources/quality/zyyx/zyyx_agile_pro_flex_fast.inst.cfg index c7d7faf575..188bdd25e5 100644 --- a/resources/quality/zyyx/zyyx_agile_pro_flex_fast.inst.cfg +++ b/resources/quality/zyyx/zyyx_agile_pro_flex_fast.inst.cfg @@ -8,7 +8,7 @@ setting_version = 5 type = quality quality_type = fast weight = 1 -material = zyyx_pro_flex +material = generic_tpu [values] layer_height = 0.3 diff --git a/resources/quality/zyyx/zyyx_agile_pro_flex_fine.inst.cfg b/resources/quality/zyyx/zyyx_agile_pro_flex_fine.inst.cfg index 1e0cc16df0..6654889c10 100644 --- a/resources/quality/zyyx/zyyx_agile_pro_flex_fine.inst.cfg +++ b/resources/quality/zyyx/zyyx_agile_pro_flex_fine.inst.cfg @@ -8,7 +8,7 @@ setting_version = 5 type = quality quality_type = fine weight = 3 -material = zyyx_pro_flex +material = generic_tpu [values] layer_height = 0.12 diff --git a/resources/quality/zyyx/zyyx_agile_pro_flex_normal.inst.cfg b/resources/quality/zyyx/zyyx_agile_pro_flex_normal.inst.cfg index 8f7be7b481..f56355100c 100644 --- a/resources/quality/zyyx/zyyx_agile_pro_flex_normal.inst.cfg +++ b/resources/quality/zyyx/zyyx_agile_pro_flex_normal.inst.cfg @@ -8,7 +8,7 @@ setting_version = 5 type = quality quality_type = normal weight = 2 -material = zyyx_pro_flex +material = generic_tpu [values] layer_height = 0.2 diff --git a/resources/quality/zyyx/zyyx_agile_pro_pla_fast.inst.cfg b/resources/quality/zyyx/zyyx_agile_pro_pla_fast.inst.cfg index 7ac7dc89b5..7ae4be06b0 100644 --- a/resources/quality/zyyx/zyyx_agile_pro_pla_fast.inst.cfg +++ b/resources/quality/zyyx/zyyx_agile_pro_pla_fast.inst.cfg @@ -8,7 +8,7 @@ setting_version = 5 type = quality quality_type = fast weight = 1 -material = zyyx_pro_pla +material = generic_pla [values] layer_height = 0.3 diff --git a/resources/quality/zyyx/zyyx_agile_pro_pla_fine.inst.cfg b/resources/quality/zyyx/zyyx_agile_pro_pla_fine.inst.cfg index 98033908f6..64c7d4afc8 100644 --- a/resources/quality/zyyx/zyyx_agile_pro_pla_fine.inst.cfg +++ b/resources/quality/zyyx/zyyx_agile_pro_pla_fine.inst.cfg @@ -8,7 +8,7 @@ setting_version = 5 type = quality quality_type = fine weight = 3 -material = zyyx_pro_pla +material = generic_pla [values] layer_height = 0.1 diff --git a/resources/quality/zyyx/zyyx_agile_pro_pla_normal.inst.cfg b/resources/quality/zyyx/zyyx_agile_pro_pla_normal.inst.cfg index b694bd9172..dbdd600ece 100644 --- a/resources/quality/zyyx/zyyx_agile_pro_pla_normal.inst.cfg +++ b/resources/quality/zyyx/zyyx_agile_pro_pla_normal.inst.cfg @@ -8,7 +8,7 @@ setting_version = 5 type = quality quality_type = normal weight = 2 -material = zyyx_pro_pla +material = generic_pla [values] layer_height = 0.2 From 87cbc3907c1a4912e30ca60f30d00d5b84fbef51 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 13 Nov 2018 12:05:14 +0100 Subject: [PATCH 19/30] Convert to string before storing in configparser Because configparser can only handle strings. Contributes to issue CURA-5929. --- plugins/LegacyProfileReader/LegacyProfileReader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/LegacyProfileReader/LegacyProfileReader.py b/plugins/LegacyProfileReader/LegacyProfileReader.py index 7974bbe829..013bab6f11 100644 --- a/plugins/LegacyProfileReader/LegacyProfileReader.py +++ b/plugins/LegacyProfileReader/LegacyProfileReader.py @@ -139,7 +139,7 @@ class LegacyProfileReader(ProfileReader): definitions = current_printer_definition.findDefinitions(key = new_setting) if definitions: if new_value != value_using_defaults and definitions[0].default_value != new_value: # Not equal to the default in the new Cura OR the default in the legacy Cura. - output_parser["values"][new_setting] = new_value # Store the setting in the profile! + output_parser["values"][new_setting] = str(new_value) # Store the setting in the profile! if len(output_parser["values"]) == 0: Logger.log("i", "A legacy profile was imported but everything evaluates to the defaults, creating an empty profile.") From 6307784a7940e4942ea964068b6a1135329071ed Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 13 Nov 2018 16:50:00 +0100 Subject: [PATCH 20/30] Fix some English grammar. [CURA-5903] --- resources/definitions/fdmprinter.def.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 3b3194f1dc..e67608f579 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -4142,7 +4142,7 @@ "minimum_support_area": { "label": "Minimum Support Area", - "description": "Minimum area size for support polygons. Polygons which area is smaller than this value will not be generated.", + "description": "Minimum area size for support polygons. Polygons which have an area smaller than this value will not be generated.", "unit": "mm²", "type": "float", "default_value": 0.0, @@ -4395,7 +4395,7 @@ "minimum_interface_area": { "label": "Minimum Support Interface Area", - "description": "Minimum area size for support interface polygons. Polygons which area are smaller than this value will not be generated.", + "description": "Minimum area size for support interface polygons. Polygons which have an area smaller than this value will not be generated.", "unit": "mm²", "type": "float", "default_value": 1.0, @@ -4409,7 +4409,7 @@ "minimum_roof_area": { "label": "Minimum Support Roof Area", - "description": "Minimum area size for the roofs of the support. Polygons which area are smaller than this value will not be generated.", + "description": "Minimum area size for the roofs of the support. Polygons which have an area smaller than this value will not be generated.", "unit": "mm²", "type": "float", "default_value": 1.0, @@ -4423,7 +4423,7 @@ "minimum_bottom_area": { "label": "Minimum Support Floor Area", - "description": "Minimum area size for the floors of the support. Polygons which area are smaller than this value will not be generated.", + "description": "Minimum area size for the floors of the support. Polygons which have an area smaller than this value will not be generated.", "unit": "mm²", "type": "float", "default_value": 1.0, From 13a9bb7eba2abf2a2790f732165b557ff7d4ec71 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 13 Nov 2018 17:03:27 +0100 Subject: [PATCH 21/30] Remove enables for min. support area in case of TreeSupport. [CURA-5903] --- resources/definitions/fdmprinter.def.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index e67608f579..325e4b6c79 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -4402,7 +4402,7 @@ "minimum_value": "0", "minimum_value_warning": "minimum_support_area", "limit_to_extruder": "support_interface_extruder_nr", - "enabled": "support_interface_enable and (support_enable or support_tree_enable)", + "enabled": "support_interface_enable and support_enable", "settable_per_mesh": true, "children": { @@ -4417,7 +4417,7 @@ "minimum_value": "0", "minimum_value_warning": "minimum_support_area", "limit_to_extruder": "support_roof_extruder_nr", - "enabled": "support_roof_enable and (support_enable or support_tree_enable)", + "enabled": "support_roof_enable and support_enable", "settable_per_mesh": true }, "minimum_bottom_area": @@ -4431,7 +4431,7 @@ "minimum_value": "0", "minimum_value_warning": "minimum_support_area", "limit_to_extruder": "support_bottom_extruder_nr", - "enabled": "support_bottom_enable and (support_enable or support_tree_enable)", + "enabled": "support_bottom_enable and support_enable", "settable_per_mesh": true } } From 66c3cc9204fdc309c993ec4b40983cdd150dc00a Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 14 Nov 2018 14:20:42 +0100 Subject: [PATCH 22/30] Prevent an error during start up --- cura/BuildVolume.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index 547c3dae71..1589f16afc 100755 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -489,7 +489,9 @@ class BuildVolume(SceneNode): def _updateRaftThickness(self): old_raft_thickness = self._raft_thickness - self._adhesion_type = self._global_container_stack.getProperty("adhesion_type", "value") + if self._global_container_stack.extruders: + # This might be called before the extruder stacks have initialised, in which case getting the adhesion_type fails + self._adhesion_type = self._global_container_stack.getProperty("adhesion_type", "value") self._raft_thickness = 0.0 if self._adhesion_type == "raft": self._raft_thickness = ( From b671a3153a0f7f7ab9745f73c300d34c6bf7a06b Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 14 Nov 2018 14:21:39 +0100 Subject: [PATCH 23/30] Catch an error getting an extruder value before extruders are added to the global stack --- cura/Settings/CuraFormulaFunctions.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cura/Settings/CuraFormulaFunctions.py b/cura/Settings/CuraFormulaFunctions.py index 1db01857f8..9ef80bd3d4 100644 --- a/cura/Settings/CuraFormulaFunctions.py +++ b/cura/Settings/CuraFormulaFunctions.py @@ -5,6 +5,7 @@ from typing import Any, List, Optional, TYPE_CHECKING from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext from UM.Settings.SettingFunction import SettingFunction +from UM.Logger import Logger if TYPE_CHECKING: from cura.CuraApplication import CuraApplication @@ -38,7 +39,11 @@ class CuraFormulaFunctions: extruder_position = int(machine_manager.defaultExtruderPosition) global_stack = machine_manager.activeMachine - extruder_stack = global_stack.extruders[str(extruder_position)] + try: + extruder_stack = global_stack.extruders[str(extruder_position)] + except KeyError: + Logger.log("w", "Value for %s of extruder %s was requested, but that extruder is not available" % (property_key, extruder_position)) + return None value = extruder_stack.getRawProperty(property_key, "value", context = context) if isinstance(value, SettingFunction): From 163f102dda027bf1337134e40d5db13450aae5e3 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 15 Nov 2018 09:38:14 +0100 Subject: [PATCH 24/30] Make extension menu items translatable If these extension plug-ins don't set their menu names, the plug-in name is used as the menu name. The plug-in names are not translated, so this appears as an untranslated string then. --- plugins/ChangeLogPlugin/ChangeLog.py | 3 ++- plugins/PostProcessingPlugin/PostProcessingPlugin.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/ChangeLogPlugin/ChangeLog.py b/plugins/ChangeLogPlugin/ChangeLog.py index 723c83a021..eeec5edf9b 100644 --- a/plugins/ChangeLogPlugin/ChangeLog.py +++ b/plugins/ChangeLogPlugin/ChangeLog.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. from UM.i18n import i18nCatalog @@ -29,6 +29,7 @@ class ChangeLog(Extension, QObject,): self._change_logs = None Application.getInstance().engineCreatedSignal.connect(self._onEngineCreated) Application.getInstance().getPreferences().addPreference("general/latest_version_changelog_shown", "2.0.0") #First version of CURA with uranium + self.setMenuName(catalog.i18nc("@item:inmenu", "Changelog")) self.addMenuItem(catalog.i18nc("@item:inmenu", "Show Changelog"), self.showChangelog) def getChangeLogs(self): diff --git a/plugins/PostProcessingPlugin/PostProcessingPlugin.py b/plugins/PostProcessingPlugin/PostProcessingPlugin.py index 1a1ea92d10..11ee610bec 100644 --- a/plugins/PostProcessingPlugin/PostProcessingPlugin.py +++ b/plugins/PostProcessingPlugin/PostProcessingPlugin.py @@ -32,7 +32,8 @@ class PostProcessingPlugin(QObject, Extension): def __init__(self, parent = None) -> None: QObject.__init__(self, parent) Extension.__init__(self) - self.addMenuItem(i18n_catalog.i18n("Modify G-Code"), self.showPopup) + self.setMenuName(i18n_catalog.i18nc("@item:inmenu", "Post Processing")) + self.addMenuItem(i18n_catalog.i18nc("@item:inmenu", "Modify G-Code"), self.showPopup) self._view = None # Loaded scripts are all scripts that can be used From 78e64944307168a886a1c0ab01a4b39e4d3144c4 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 15 Nov 2018 15:13:36 +0100 Subject: [PATCH 25/30] Read SDK version from new semver field The sdk_version field should stay the ordinary plain number (the major version number of the semver field) so that older Cura versions don't break. Newly built packages will get built with both sdk_version_semver and the normal sdk_version, so that the packages can be read with any Cura version from 3.6 onwards. Contributes to issue CURA-5940. --- plugins/Toolbox/src/Toolbox.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 4f3722d7ba..e491a06770 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -511,7 +511,8 @@ class Toolbox(QObject, Extension): # version, we also need to check if the current one has a lower SDK version. If so, this package should also # be upgradable. elif remote_version == local_version: - can_upgrade = local_package.get("sdk_version", 0) < remote_package.get("sdk_version", 0) + # First read sdk_version_semver. If that doesn't exist, read just sdk_version (old version system). + can_upgrade = local_package.get("sdk_version_semver", local_package.get("sdk_version", 0)) < remote_package.get("sdk_version_semver", remote_package.get("sdk_version", 0)) return can_upgrade From e9216936d741acf435f260b1bd5b10baffddb594 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 15 Nov 2018 15:20:21 +0100 Subject: [PATCH 26/30] Compare SDK version as Version instances This allows it to compare versions that are remote as integer and local as string, or vice-versa. Much more robust. Contributes to issue CURA-5940. --- plugins/Toolbox/src/Toolbox.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index e491a06770..562a964f01 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -512,7 +512,9 @@ class Toolbox(QObject, Extension): # be upgradable. elif remote_version == local_version: # First read sdk_version_semver. If that doesn't exist, read just sdk_version (old version system). - can_upgrade = local_package.get("sdk_version_semver", local_package.get("sdk_version", 0)) < remote_package.get("sdk_version_semver", remote_package.get("sdk_version", 0)) + remote_sdk_version = Version(remote_package.get("sdk_version_semver", remote_package.get("sdk_version", 0))) + local_sdk_version = Version(local_package.get("sdk_version_semver", local_package.get("sdk_version", 0))) + can_upgrade = local_sdk_version < remote_sdk_version return can_upgrade From 2bf4354fed244e47ab00da8528f1db7b6a906a15 Mon Sep 17 00:00:00 2001 From: Aleksei S Date: Thu, 15 Nov 2018 15:57:34 +0100 Subject: [PATCH 27/30] Removed minimum_value for the setting support_roof_offset and support_bottom_offset CURA-5880 --- resources/definitions/fdmprinter.def.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 325e4b6c79..7cb6720f27 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -4456,7 +4456,6 @@ "description": "Amount of offset applied to the roofs of the support.", "unit": "mm", "type": "float", - "minimum_value": "0", "default_value": 0.0, "value": "extruderValue(support_roof_extruder_nr, 'support_interface_offset')", "maximum_value": "extruderValue(support_extruder_nr, 'support_offset')", @@ -4471,7 +4470,6 @@ "description": "Amount of offset applied to the floors of the support.", "unit": "mm", "type": "float", - "minimum_value": "0", "default_value": 0.0, "value": "extruderValue(support_bottom_extruder_nr, 'support_interface_offset')", "maximum_value": "extruderValue(support_extruder_nr, 'support_offset')", From 240ac1f06b6458d154d84ce326a3dd8e16777aa1 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 16 Nov 2018 13:03:45 +0100 Subject: [PATCH 28/30] Clarify some setting descriptions --- resources/definitions/fdmextruder.def.json | 2 +- resources/definitions/fdmprinter.def.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/definitions/fdmextruder.def.json b/resources/definitions/fdmextruder.def.json index 19c9e92d18..55556f5764 100644 --- a/resources/definitions/fdmextruder.def.json +++ b/resources/definitions/fdmextruder.def.json @@ -78,7 +78,7 @@ "machine_extruder_start_code": { "label": "Extruder Start G-Code", - "description": "Start g-code to execute whenever turning the extruder on.", + "description": "Start g-code to execute whenever this extruder is switched to.", "type": "str", "default_value": "", "settable_per_mesh": false, diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 7cb6720f27..b1812ae705 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -2406,7 +2406,7 @@ "switch_extruder_retraction_amount": { "label": "Nozzle Switch Retraction Distance", - "description": "The amount of retraction: Set at 0 for no retraction at all. This should generally be the same as the length of the heat zone.", + "description": "The amount of retraction when switching extruder. Set to 0 for no retraction at all. This should generally be the same as the length of the heat zone.", "type": "float", "unit": "mm", "enabled": "retraction_enable", From 93bd5fee5387e45ea880ee1fc6125790546e465a Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 16 Nov 2018 13:44:46 +0100 Subject: [PATCH 29/30] Fix wrong push free shadow for one at a time. It was applying the size twice. CURA-5822 --- cura/Scene/ConvexHullDecorator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/Scene/ConvexHullDecorator.py b/cura/Scene/ConvexHullDecorator.py index 39124c5ba3..0c03ae615b 100644 --- a/cura/Scene/ConvexHullDecorator.py +++ b/cura/Scene/ConvexHullDecorator.py @@ -272,7 +272,7 @@ class ConvexHullDecorator(SceneNodeDecorator): head_and_fans = self._getHeadAndFans().intersectionConvexHulls(mirrored) # Min head hull is used for the push free - convex_hull = self._compute2DConvexHeadFull() + convex_hull = self._compute2DConvexHull() if convex_hull: return convex_hull.getMinkowskiHull(head_and_fans) return None From f10bd28c4ae8084f9b8f8f343b3b8471a764a2a4 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 16 Nov 2018 17:25:32 +0100 Subject: [PATCH 30/30] Improve grammar and do similar fix for extruder end g-code Contributes to issue CURA-5906. --- resources/definitions/fdmextruder.def.json | 4 ++-- resources/definitions/fdmprinter.def.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/definitions/fdmextruder.def.json b/resources/definitions/fdmextruder.def.json index 55556f5764..cb49b1e128 100644 --- a/resources/definitions/fdmextruder.def.json +++ b/resources/definitions/fdmextruder.def.json @@ -78,7 +78,7 @@ "machine_extruder_start_code": { "label": "Extruder Start G-Code", - "description": "Start g-code to execute whenever this extruder is switched to.", + "description": "Start g-code to execute when switching to this extruder.", "type": "str", "default_value": "", "settable_per_mesh": false, @@ -124,7 +124,7 @@ "machine_extruder_end_code": { "label": "Extruder End G-Code", - "description": "End g-code to execute whenever turning the extruder off.", + "description": "End g-code to execute when switching away from this extruder.", "type": "str", "default_value": "", "settable_per_mesh": false, diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index b1812ae705..c015ab8ccb 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -2406,7 +2406,7 @@ "switch_extruder_retraction_amount": { "label": "Nozzle Switch Retraction Distance", - "description": "The amount of retraction when switching extruder. Set to 0 for no retraction at all. This should generally be the same as the length of the heat zone.", + "description": "The amount of retraction when switching extruders. Set to 0 for no retraction at all. This should generally be the same as the length of the heat zone.", "type": "float", "unit": "mm", "enabled": "retraction_enable",