From 34f394a98d6224486cd16013ea624161bf146cf6 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 7 Jul 2023 10:13:13 +0200 Subject: [PATCH 01/45] Ensure that class variable is defined in init Boyscouting. CURA-10717 --- UM/PluginRegistry.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/UM/PluginRegistry.py b/UM/PluginRegistry.py index acef0dbe2e..17ff8d24f6 100644 --- a/UM/PluginRegistry.py +++ b/UM/PluginRegistry.py @@ -89,6 +89,7 @@ def __init__(self, application: "Application", parent: QObject = None) -> None: self._distrusted_plugin_ids: List[str] = [] self._trust_checker: Optional[Trust] = None self._changed_activated_plugins_current_session: Set[str] = set() + self._plugin_config_filename: str = "" pluginRemoved = pyqtSignal(str) @@ -109,7 +110,7 @@ def initializeBeforePluginsAreLoaded(self) -> None: # File to store plugin info, such as which ones to install/remove and which ones are disabled. # At this point we can load this here because we already know the actual Application name, so the directory name - self._plugin_config_filename: str = os.path.join(os.path.abspath(config_path), "plugins.json") + self._plugin_config_filename = os.path.join(os.path.abspath(config_path), "plugins.json") from UM.Settings.ContainerRegistry import ContainerRegistry container_registry = ContainerRegistry.getInstance() From e9918030e44d6fe9c85b00fdba22951980bc651e Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 7 Jul 2023 10:16:35 +0200 Subject: [PATCH 02/45] Minor codestyle fixes Boyscouting CURA-10717 --- UM/PluginRegistry.py | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/UM/PluginRegistry.py b/UM/PluginRegistry.py index 17ff8d24f6..e77a7dd42b 100644 --- a/UM/PluginRegistry.py +++ b/UM/PluginRegistry.py @@ -48,12 +48,14 @@ class PluginRegistry(QObject): def __init__(self, application: "Application", parent: QObject = None) -> None: if PluginRegistry.__instance is not None: - raise RuntimeError("Try to create singleton '%s' more than once" % self.__class__.__name__) + raise RuntimeError(f"Try to create singleton '{self.__class__.__name__}' more than once") super().__init__(parent) PluginRegistry.__instance = self - self.preloaded_plugins: List[str] = [] # List of plug-in names that must be loaded before the rest, if the plug-ins are available. They are loaded in this order too. + # List of plug-in names that must be loaded before the rest, if the plug-ins are available. + # They are loaded in this order too. + self.preloaded_plugins: List[str] = [] self._application: Application = application self._api_version: Version = application.getAPIVersion() @@ -72,11 +74,11 @@ def __init__(self, application: "Application", parent: QObject = None) -> None: self._plugins_to_remove: List[str] = [] self._plugins: Dict[str, types.ModuleType] = {} - self._found_plugins: Dict[str, types.ModuleType] = {} # Cache to speed up _findPlugin + self._found_plugins: Dict[str, types.ModuleType] = {} # Cache to speed up _findPlugin self._plugin_objects: Dict[str, PluginObject] = {} self._plugin_locations: List[str] = [] - self._plugin_folder_cache: Dict[str, List[Tuple[str, str]]] = {} # Cache to speed up _locatePlugin + self._plugin_folder_cache: Dict[str, List[Tuple[str, str]]] = {} # Cache to speed up _locatePlugin self._bundled_plugin_cache: Dict[str, bool] = {} @@ -125,7 +127,7 @@ def initializeBeforePluginsAreLoaded(self) -> None: self._disabled_plugins = data["disabled"] self._plugins_to_install = data["to_install"] self._plugins_to_remove = data["to_remove"] - except: + except Exception: Logger.logException("e", "Failed to load plugin configuration file '%s'", self._plugin_config_filename) # Also load data from preferences, where the plugin info used to be saved @@ -175,7 +177,7 @@ def _savePluginData(self) -> None: "to_remove": self._plugins_to_remove, }) f.write(data) - except: + except Exception: # Since we're writing to file (and waiting for a lock), there are a few things that can go wrong. # There is no need to crash the application for this, but it is a failure that we want to log. Logger.logException("e", "Unable to save the plugin data.") @@ -193,10 +195,6 @@ def _savePluginData(self) -> None: # Available: A plugin which is not installed but could be. # Installed: A plugin which is installed locally in Cura. - #=============================================================================== - # PUBLIC METHODS - #=============================================================================== - # Add a plugin location to the list of locations to search: def addPluginLocation(self, location: str) -> None: if not os.path.isdir(location): @@ -605,10 +603,6 @@ def _removePlugin(self, plugin_id: str) -> None: except EnvironmentError as e: Logger.error("Unable to remove plug-in {plugin_id}: {err}".format(plugin_id = plugin_id, err = str(e))) -#=============================================================================== -# PRIVATE METHODS -#=============================================================================== - def _getPluginIdFromFile(self, filename: str) -> Optional[str]: plugin_id = None try: @@ -704,7 +698,7 @@ def _findPlugin(self, plugin_id: str) -> Optional[types.ModuleType]: with open(central_storage_file, "r", encoding = "utf-8") as file_stream: if not self._handleCentralStorage(file_stream.read(), plugin_final_path, is_bundled_plugin = self.isBundledPlugin(plugin_id)): return None - except: + except Exception: pass try: file, path, desc = imp.find_module(plugin_id, [final_location]) From d35faf4e8fd060afead6e15fae82183c38048bf3 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 7 Jul 2023 10:35:05 +0200 Subject: [PATCH 03/45] Move documentation to new style Boyscouting CURA-10717 --- UM/PluginRegistry.py | 185 ++++++++++++++++++++++++++++--------------- 1 file changed, 122 insertions(+), 63 deletions(-) diff --git a/UM/PluginRegistry.py b/UM/PluginRegistry.py index e77a7dd42b..17b017f87a 100644 --- a/UM/PluginRegistry.py +++ b/UM/PluginRegistry.py @@ -41,11 +41,25 @@ class PluginRegistry(QObject): them as plugins. Each plugin module is expected to be a directory with and `__init__` file defining a `getMetaData` and a `register` function. + Terms for plugins used: + Enabled (active): A plugin which is installed and currently enabled. + Disabled: A plugin which is installed but not currently enabled. + Available: A plugin which is not installed but could be. + Installed: A plugin which is installed locally in Cura. + For more details, see the [plugins] file. [plugins]: docs/plugins.md """ + pluginsEnabledOrDisabledChanged = pyqtSignal() + pluginRemoved = pyqtSignal(str) + + # Indicates that a specific plugin is currently being loaded. If the plugin_id is empty, it means that no plugin + # is currently being loaded. + pluginLoadStarted = pyqtSignal(str, arguments=["plugin_id"]) + supportedPluginExtensionsChanged = pyqtSignal() + def __init__(self, application: "Application", parent: QObject = None) -> None: if PluginRegistry.__instance is not None: raise RuntimeError(f"Try to create singleton '{self.__class__.__name__}' more than once") @@ -93,8 +107,6 @@ def __init__(self, application: "Application", parent: QObject = None) -> None: self._changed_activated_plugins_current_session: Set[str] = set() self._plugin_config_filename: str = "" - pluginRemoved = pyqtSignal(str) - def setCheckIfTrusted(self, check_if_trusted: bool, debug_mode: bool = False) -> None: self._check_if_trusted = check_if_trusted if self._check_if_trusted: @@ -182,29 +194,23 @@ def _savePluginData(self) -> None: # There is no need to crash the application for this, but it is a failure that we want to log. Logger.logException("e", "Unable to save the plugin data.") - # TODO: - # - [ ] Improve how metadata is stored. It should not be in the 'plugin' prop - # of the dictionary item. - # - [ ] Remove usage of "active" in favor of "enabled". - # - [ ] Switch self._disabled_plugins to self._plugins_disabled - # - [ ] External plugins only appear in installed after restart - # - # NOMENCLATURE: - # Enabled (active): A plugin which is installed and currently enabled. - # Disabled: A plugin which is installed but not currently enabled. - # Available: A plugin which is not installed but could be. - # Installed: A plugin which is installed locally in Cura. - - # Add a plugin location to the list of locations to search: def addPluginLocation(self, location: str) -> None: + """ + Add a plugin location to the list of locations to search + :param location: The location (folder) to look for + """ if not os.path.isdir(location): Logger.warning("Plugin location {0} must be a folder.".format(location)) return self._plugin_locations.append(location) - # Check if all required plugins are loaded: def checkRequiredPlugins(self, required_plugins: List[str]) -> bool: + """ + Check if all required plugins are loaded + :param required_plugins: List of all the plugin id's that must be loaded. + :return: True if all plugins in the list are loaded, false if not. + """ plugins = self._findInstalledPlugins() for plugin_id in required_plugins: if plugin_id not in plugins: @@ -212,10 +218,12 @@ def checkRequiredPlugins(self, required_plugins: List[str]) -> bool: return False return True - pluginsEnabledOrDisabledChanged = pyqtSignal() - - # Remove plugin from the list of enabled plugins and save to preferences: def disablePlugin(self, plugin_id: str) -> None: + """ + Remove plugin from the list of enabled plugins and save to preferences + :param plugin_id: Id of the plugin to be disabled + :return: + """ if plugin_id not in self._disabled_plugins: self._disabled_plugins.append(plugin_id) if plugin_id not in self._changed_activated_plugins_current_session: @@ -225,8 +233,12 @@ def disablePlugin(self, plugin_id: str) -> None: self.pluginsEnabledOrDisabledChanged.emit() self._savePluginData() - # Add plugin to the list of enabled plugins and save to preferences: def enablePlugin(self, plugin_id: str) -> None: + """ + Add plugin to the list of enabled plugins and save to preferences: + :param plugin_id: Id of the plugin to be enabled + :return: + """ if plugin_id in self._disabled_plugins: self._disabled_plugins.remove(plugin_id) if plugin_id not in self._changed_activated_plugins_current_session: @@ -236,24 +248,30 @@ def enablePlugin(self, plugin_id: str) -> None: self.pluginsEnabledOrDisabledChanged.emit() self._savePluginData() - # Get a list of enabled plugins: def getActivePlugins(self) -> List[str]: + """ + Get the list of enabled plugins + :return: + """ plugin_list = [] for plugin_id in self._all_plugins: if self.isActivePlugin(plugin_id): plugin_list.append(plugin_id) return plugin_list - # Get a list of all metadata matching a certain subset of metadata: - # \param kwargs Keyword arguments. - # Possible keywords: - # - filter: \type{dict} The subset of metadata that should be matched. - # - active_only: Boolean, True when only active plugin metadata should - # be returned. - def getAllMetaData(self, **kwargs: Any): + def getAllMetaData(self, **kwargs: Any) -> List[Dict[str, Any]]: + """ + Get a list of all metadata matching a certain subset of metadata + :param kwargs: + Possible keywords: + - filter: \type{dict} The subset of metadata that should be matched. + - active_only: Boolean, True when only active plugin metadata should + be returned. + :return: List of metadata + """ data_filter = kwargs.get("filter", {}) active_only = kwargs.get("active_only", False) - metadata_list = [] + metadata_list: List[Dict[str, Any]] = [] for plugin_id in self._all_plugins: if active_only and (plugin_id in self._disabled_plugins or plugin_id in self._outdated_plugins): continue @@ -262,8 +280,11 @@ def getAllMetaData(self, **kwargs: Any): metadata_list.append(plugin_metadata) return metadata_list - # Get a list of disabled plugins: def getDisabledPlugins(self) -> List[str]: + """ + Get a list of disabled plugins + :return: + """ return self._disabled_plugins def getCurrentSessionActivationChangedPlugins(self) -> Set[str]: @@ -271,10 +292,13 @@ def getCurrentSessionActivationChangedPlugins(self) -> Set[str]: en-/disabled after the last start-up status""" return self._changed_activated_plugins_current_session - # Get a list of installed plugins: - # NOTE: These are plugins which have already been registered. This list is - # actually populated by the private _findInstalledPlugins() method. def getInstalledPlugins(self) -> List[str]: + """ + Get a list of installed plugins: + NOTE: These are plugins which have already been registered. This list is actually populated by the protected + _findInstalledPlugins() method. + :return: + """ plugins = self._plugins_installed.copy() for plugin_id in self._plugins_to_remove: if plugin_id in plugins: @@ -284,10 +308,13 @@ def getInstalledPlugins(self) -> List[str]: plugins.append(plugin_id) return sorted(plugins) - # Get the metadata for a certain plugin: - # NOTE: InvalidMetaDataError is raised when no metadata can be found or - # the metadata misses the right keys. def getMetaData(self, plugin_id: str) -> Dict[str, Any]: + """ + Get the metadata for a certain plugin + NOTE: InvalidMetaDataError is raised when no metadata can be found or the metadata misses the right keys. + :param plugin_id: + :return: Metadata object of the plugin. Empty dict if not found. + """ if plugin_id not in self._metadata: try: if not self._populateMetaData(plugin_id): @@ -302,7 +329,7 @@ def installPlugin(self, plugin_path: str) -> Optional[Dict[str, str]]: plugin_path = QUrl(plugin_path).toLocalFile() plugin_id = self._getPluginIdFromFile(plugin_path) - if plugin_id is None: #Failed to load. + if plugin_id is None: # Failed to load. return None # Remove it from the to-be-removed list if it's there @@ -310,7 +337,7 @@ def installPlugin(self, plugin_path: str) -> Optional[Dict[str, str]]: self._plugins_to_remove.remove(plugin_id) self._savePluginData() - # Copy the plugin file to the cache directory so it can later be used for installation + # Copy the plugin file to the cache directory so that it can later be used for installation cache_dir = os.path.join(Resources.getCacheStoragePath(), "plugins") if not os.path.exists(cache_dir): os.makedirs(cache_dir, exist_ok = True) @@ -328,13 +355,21 @@ def installPlugin(self, plugin_path: str) -> Optional[Dict[str, str]]: result = {"status": "ok", "id": "", - "message": i18n_catalog.i18nc("@info:status", "The plugin has been installed.\nPlease re-start the application to activate the plugin."), + "message": i18n_catalog.i18nc("@info:status", + "The plugin has been installed.\n" + "Please re-start the application to activate the plugin."), } return result - # Check by ID if a plugin is active (enabled): def isActivePlugin(self, plugin_id: str) -> bool: - if plugin_id not in self._disabled_plugins and plugin_id not in self._outdated_plugins and plugin_id in self._all_plugins: + """ + Check by ID if a plugin is active (enabled): + :param plugin_id: + :return: + """ + if plugin_id not in self._disabled_plugins and \ + plugin_id not in self._outdated_plugins and \ + plugin_id in self._all_plugins: return True return False @@ -363,10 +398,6 @@ def isBundledPlugin(self, plugin_id: str) -> bool: self._bundled_plugin_cache[plugin_id] = is_bundled return is_bundled - # Indicates that a specific plugin is currently being loaded. If the plugin_id is empty, it means that no plugin - # is currently being loaded. - pluginLoadStarted = pyqtSignal(str, arguments = ["plugin_id"]) - def loadPlugins(self, metadata: Optional[Dict[str, Any]] = None) -> None: """Load all plugins matching a certain set of metadata @@ -394,7 +425,8 @@ def loadPlugins(self, metadata: Optional[Dict[str, Any]] = None) -> None: except TrustException: Logger.error("Plugin {} was not loaded because it could not be verified.", plugin_id) message_text = i18n_catalog.i18nc("@error:untrusted", - "Plugin {} was not loaded because it could not be verified.", plugin_id) + "Plugin {} was not loaded because it could not be verified.", + plugin_id) Message(text = message_text, message_type = Message.MessageType.ERROR).show() continue @@ -414,13 +446,20 @@ def loadPlugins(self, metadata: Optional[Dict[str, Any]] = None) -> None: self.pluginLoadStarted.emit("") Logger.log("d", "Loading all plugins took %s seconds", time.time() - start_time) - # Checks if the given plugin API version is compatible with the current version. def isPluginApiVersionCompatible(self, plugin_api_version: "Version") -> bool: + """ + Checks if the given plugin API version is compatible with the current version. + :param plugin_api_version: + :return: + """ return plugin_api_version.getMajor() == self._api_version.getMajor() \ and plugin_api_version.getMinor() <= self._api_version.getMinor() - # Load a single plugin by ID: def loadPlugin(self, plugin_id: str) -> None: + """ + Load a single plugin by ID + :param plugin_id: + """ # If plugin has already been loaded, do not load it again: if plugin_id in self._plugins: Logger.log("w", "Plugin %s was already loaded", plugin_id) @@ -560,8 +599,12 @@ def uninstallPlugin(self, plugin_id: str) -> Dict[str, str]: } return result - # Installs the given plugin file. It will overwrite the existing plugin if present. def _installPlugin(self, plugin_id: str, plugin_path: str) -> None: + """ + Installs the given plugin file. It will overwrite the existing plugin if present. + :param plugin_id: + :param plugin_path: + """ Logger.log("i", "Attempting to install a new plugin %s from file '%s'", plugin_id, plugin_path) local_plugin_path = os.path.join(Resources.getStoragePath(Resources.Resources), "plugins") @@ -583,14 +626,18 @@ def _installPlugin(self, plugin_id: str, plugin_path: str) -> None: extracted_path = zip_ref.extract(info.filename, path = plugin_folder) permissions = os.stat(extracted_path).st_mode os.chmod(extracted_path, permissions | stat.S_IEXEC) # Make these files executable. - except: # Installing a new plugin should never crash the application. + except Exception: # Installing a new plugin should never crash the application. Logger.logException("e", "An exception occurred while installing plugin {path}".format(path = plugin_path)) if plugin_id in self._disabled_plugins: self._disabled_plugins.remove(plugin_id) - # Removes the given plugin. def _removePlugin(self, plugin_id: str) -> None: + """ + Removes the given plugin. + :param plugin_id: + :return: + """ plugin_folder = os.path.join(Resources.getStoragePath(Resources.Resources), "plugins") plugin_path = os.path.join(plugin_folder, plugin_id) @@ -619,8 +666,12 @@ def _getPluginIdFromFile(self, filename: str) -> Optional[str]: return None # Signals that loading this failed. return plugin_id - # Returns a list of all possible plugin ids in the plugin locations: def _findInstalledPlugins(self, paths = None) -> List[str]: + """ + Returns a list of all possible plugin ids in the plugin locations + :param paths: + :return: + """ plugin_ids = [] if not paths: @@ -645,7 +696,8 @@ def _findInstalledPlugins(self, paths = None) -> List[str]: return plugin_ids def _findPlugin(self, plugin_id: str) -> Optional[types.ModuleType]: - """Try to find a module implementing a plugin + """ + Try to find a module implementing a plugin :param plugin_id: The name of the plugin to find :returns: module if it was found (and, if 'self._check_if_trusted' is set, also secure), None otherwise @@ -776,8 +828,14 @@ def _handleCentralStorage(self, file_data: str, plugin_path: str, is_bundled_plu Logger.logException("w", f"Can't move file {file_to_move[0]} to central storage for '{plugin_path}'.") return True - # Load the plugin data from the stream and in-place update the metadata. - def _parsePluginInfo(self, plugin_id, file_data, meta_data): + def _parsePluginInfo(self, plugin_id: str, file_data: str, meta_data: Dict[str, Any]) -> None: + """ + Load the plugin data from the stream and in-place update the metadata. + :param plugin_id: Id of the plugin + :param file_data: The data as read from the file + :param meta_data: Dict with the metadata of the plugin + :return: + """ try: meta_data["plugin"] = json.loads(file_data) except json.decoder.JSONDecodeError: @@ -840,7 +898,7 @@ def _populateMetaData(self, plugin_id: str) -> bool: Logger.logException("e", "Unable to find the required plugin.json file for plugin %s", plugin_id) raise InvalidMetaDataError(plugin_id) except UnicodeDecodeError: - Logger.logException("e", "The plug-in metadata file for plug-in {plugin_id} is corrupt.".format(plugin_id = plugin_id)) + Logger.logException("e", f"The plug-in metadata file for plug-in {plugin_id} is corrupt.".format(plugin_id = plugin_id)) raise InvalidMetaDataError(plugin_id) except EnvironmentError as e: Logger.logException("e", "Can't open the metadata file for plug-in {plugin_id}: {err}".format(plugin_id = plugin_id, err = str(e))) @@ -868,10 +926,13 @@ def _populateMetaData(self, plugin_id: str) -> bool: self._metadata[plugin_id] = meta_data return True - # Check if a certain dictionary contains a certain subset of key/value pairs - # \param dictionary \type{dict} The dictionary to search - # \param subset \type{dict} The subset to search for def _subsetInDict(self, dictionary: Dict[Any, Any], subset: Dict[Any, Any]) -> bool: + """ + Check if a certain dictionary contains a certain subset of key/value pairs + :param dictionary: Target dictionary to search in + :param subset: The subset to search for + :return: + """ for key in subset: if key not in dictionary: return False @@ -896,7 +957,7 @@ def _addPluginObject(self, plugin_object: PluginObject, plugin_id: str, plugin_t self._plugin_objects[plugin_id] = plugin_object try: self._type_register_map[plugin_type](plugin_object) - except Exception as e: + except Exception: Logger.logException("e", "Unable to add plugin %s", plugin_id) def addSupportedPluginExtension(self, extension: str, description: str) -> None: @@ -904,8 +965,6 @@ def addSupportedPluginExtension(self, extension: str, description: str) -> None: self._supported_file_types[extension] = description self.supportedPluginExtensionsChanged.emit() - supportedPluginExtensionsChanged = pyqtSignal() - @pyqtProperty("QStringList", notify=supportedPluginExtensionsChanged) def supportedPluginExtensions(self) -> List[str]: file_types = [] From a8d370a2a098bbad08f9cc3fa6e5f76ea89aad3e Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 7 Jul 2023 10:37:05 +0200 Subject: [PATCH 04/45] Convert functions that should have been static to static method Boyscouting CURA-10717 --- UM/PluginRegistry.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/UM/PluginRegistry.py b/UM/PluginRegistry.py index 17b017f87a..94103917e3 100644 --- a/UM/PluginRegistry.py +++ b/UM/PluginRegistry.py @@ -650,7 +650,8 @@ def _removePlugin(self, plugin_id: str) -> None: except EnvironmentError as e: Logger.error("Unable to remove plug-in {plugin_id}: {err}".format(plugin_id = plugin_id, err = str(e))) - def _getPluginIdFromFile(self, filename: str) -> Optional[str]: + @staticmethod + def _getPluginIdFromFile(filename: str) -> Optional[str]: plugin_id = None try: with zipfile.ZipFile(filename, "r") as zip_ref: @@ -806,7 +807,8 @@ def _locatePlugin(self, plugin_id: str, folder: str) -> Optional[str]: return None - def _handleCentralStorage(self, file_data: str, plugin_path: str, is_bundled_plugin: bool = False) -> bool: + @staticmethod + def _handleCentralStorage(file_data: str, plugin_path: str, is_bundled_plugin: bool = False) -> bool: """ Plugins can indicate that they want certain things to be stored in a central location. In the case of a signed plugin you *must* do this by means of the central_storage.json file. @@ -828,7 +830,8 @@ def _handleCentralStorage(self, file_data: str, plugin_path: str, is_bundled_plu Logger.logException("w", f"Can't move file {file_to_move[0]} to central storage for '{plugin_path}'.") return True - def _parsePluginInfo(self, plugin_id: str, file_data: str, meta_data: Dict[str, Any]) -> None: + @staticmethod + def _parsePluginInfo(plugin_id: str, file_data: str, meta_data: Dict[str, Any]) -> None: """ Load the plugin data from the stream and in-place update the metadata. :param plugin_id: Id of the plugin @@ -926,7 +929,8 @@ def _populateMetaData(self, plugin_id: str) -> bool: self._metadata[plugin_id] = meta_data return True - def _subsetInDict(self, dictionary: Dict[Any, Any], subset: Dict[Any, Any]) -> bool: + @staticmethod + def _subsetInDict(dictionary: Dict[Any, Any], subset: Dict[Any, Any]) -> bool: """ Check if a certain dictionary contains a certain subset of key/value pairs :param dictionary: Target dictionary to search in From 1debee5ec7888e8cf3ab06642f009a7aaea0fc18 Mon Sep 17 00:00:00 2001 From: nallath Date: Fri, 7 Jul 2023 08:39:10 +0000 Subject: [PATCH 05/45] update translations --- resources/i18n/cs_CZ/uranium.po | 2 +- resources/i18n/de_DE/uranium.po | 2 +- resources/i18n/es_ES/uranium.po | 2 +- resources/i18n/fi_FI/uranium.po | 2 +- resources/i18n/fr_FR/uranium.po | 2 +- resources/i18n/hu_HU/uranium.po | 2 +- resources/i18n/it_IT/uranium.po | 2 +- resources/i18n/ja_JP/uranium.po | 2 +- resources/i18n/ko_KR/uranium.po | 2 +- resources/i18n/nl_NL/uranium.po | 2 +- resources/i18n/pl_PL/uranium.po | 2 +- resources/i18n/pt_BR/uranium.po | 2 +- resources/i18n/pt_PT/uranium.po | 2 +- resources/i18n/ru_RU/uranium.po | 2 +- resources/i18n/tr_TR/uranium.po | 2 +- resources/i18n/uranium.pot | 62 ++++++++++++++++----------------- resources/i18n/zh_CN/uranium.po | 2 +- resources/i18n/zh_TW/uranium.po | 2 +- 18 files changed, 48 insertions(+), 48 deletions(-) diff --git a/resources/i18n/cs_CZ/uranium.po b/resources/i18n/cs_CZ/uranium.po index c42b1f2519..239eefd6f8 100644 --- a/resources/i18n/cs_CZ/uranium.po +++ b/resources/i18n/cs_CZ/uranium.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Uranium 5.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-07 08:39+0000\n" "PO-Revision-Date: 2022-04-26 00:14+0200\n" "Last-Translator: Miroslav Šustek \n" "Language-Team: DenyCZ \n" diff --git a/resources/i18n/de_DE/uranium.po b/resources/i18n/de_DE/uranium.po index d40e9fda6d..dbc278811d 100644 --- a/resources/i18n/de_DE/uranium.po +++ b/resources/i18n/de_DE/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-07 08:39+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/es_ES/uranium.po b/resources/i18n/es_ES/uranium.po index ce699b1c17..06d2a302c2 100644 --- a/resources/i18n/es_ES/uranium.po +++ b/resources/i18n/es_ES/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-07 08:39+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/fi_FI/uranium.po b/resources/i18n/fi_FI/uranium.po index 2536199cbb..2acd3c740b 100644 --- a/resources/i18n/fi_FI/uranium.po +++ b/resources/i18n/fi_FI/uranium.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Uranium 5.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-07 08:39+0000\n" "PO-Revision-Date: 2017-09-27 12:27+0200\n" "Last-Translator: Bothof \n" "Language-Team: Finnish \n" diff --git a/resources/i18n/fr_FR/uranium.po b/resources/i18n/fr_FR/uranium.po index 862b24f052..273208f782 100644 --- a/resources/i18n/fr_FR/uranium.po +++ b/resources/i18n/fr_FR/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-07 08:39+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/hu_HU/uranium.po b/resources/i18n/hu_HU/uranium.po index 10f6f721e5..7aed4e8211 100644 --- a/resources/i18n/hu_HU/uranium.po +++ b/resources/i18n/hu_HU/uranium.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Uranium 5.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-07 08:39+0000\n" "PO-Revision-Date: 2019-10-10 22:30+0200\n" "Last-Translator: Nagy Attila\n" "Language-Team: AT-VLOG \n" diff --git a/resources/i18n/it_IT/uranium.po b/resources/i18n/it_IT/uranium.po index 5a2858b9e6..3f00151427 100644 --- a/resources/i18n/it_IT/uranium.po +++ b/resources/i18n/it_IT/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-07 08:39+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/ja_JP/uranium.po b/resources/i18n/ja_JP/uranium.po index f627239f3e..92bea5b94e 100644 --- a/resources/i18n/ja_JP/uranium.po +++ b/resources/i18n/ja_JP/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-07 08:39+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/ko_KR/uranium.po b/resources/i18n/ko_KR/uranium.po index f50fe99714..3d77e44325 100644 --- a/resources/i18n/ko_KR/uranium.po +++ b/resources/i18n/ko_KR/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-07 08:39+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/nl_NL/uranium.po b/resources/i18n/nl_NL/uranium.po index cb7b8ce870..57bf080da6 100644 --- a/resources/i18n/nl_NL/uranium.po +++ b/resources/i18n/nl_NL/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-07 08:39+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/pl_PL/uranium.po b/resources/i18n/pl_PL/uranium.po index bb88e3ed21..61b28043c3 100644 --- a/resources/i18n/pl_PL/uranium.po +++ b/resources/i18n/pl_PL/uranium.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Uranium 5.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-07 08:39+0000\n" "PO-Revision-Date: 2019-11-15 15:35+0100\n" "Last-Translator: Mariusz 'Virgin71' Matłosz \n" "Language-Team: reprapy.pl\n" diff --git a/resources/i18n/pt_BR/uranium.po b/resources/i18n/pt_BR/uranium.po index 5c07d56c42..97b9f35d3f 100644 --- a/resources/i18n/pt_BR/uranium.po +++ b/resources/i18n/pt_BR/uranium.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Uranium 5.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-07 08:39+0000\n" "PO-Revision-Date: 2022-04-25 07:03+0200\n" "Last-Translator: Cláudio Sampaio \n" "Language-Team: Cláudio Sampaio\n" diff --git a/resources/i18n/pt_PT/uranium.po b/resources/i18n/pt_PT/uranium.po index 57630d4313..035d0d5332 100644 --- a/resources/i18n/pt_PT/uranium.po +++ b/resources/i18n/pt_PT/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-07 08:39+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/ru_RU/uranium.po b/resources/i18n/ru_RU/uranium.po index 1df9c05df3..872741cfb7 100644 --- a/resources/i18n/ru_RU/uranium.po +++ b/resources/i18n/ru_RU/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-07 08:39+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/tr_TR/uranium.po b/resources/i18n/tr_TR/uranium.po index 8ecca11761..a96cab9a74 100644 --- a/resources/i18n/tr_TR/uranium.po +++ b/resources/i18n/tr_TR/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-07 08:39+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/uranium.pot b/resources/i18n/uranium.pot index fcee1f415f..ed5fbc8cac 100644 --- a/resources/i18n/uranium.pot +++ b/resources/i18n/uranium.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-07 08:39+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -598,122 +598,122 @@ msgid "Local Container Provider" msgstr "" msgctxt "description" -msgid "Provides the Rotate tool." +msgid "Enables saving to local files." msgstr "" msgctxt "name" -msgid "Rotate Tool" +msgid "Local File Output Device" msgstr "" msgctxt "description" -msgid "Provides the Move tool." +msgid "Makes it possible to read Wavefront OBJ files." msgstr "" msgctxt "name" -msgid "Move Tool" +msgid "Wavefront OBJ Reader" msgstr "" msgctxt "description" -msgid "Provides the tool to manipulate the camera." +msgid "Provides support for writing STL files." msgstr "" msgctxt "name" -msgid "Camera Tool" +msgid "STL Writer" msgstr "" msgctxt "description" -msgid "Provides the Mirror tool." +msgid "Provides support for reading STL files." msgstr "" msgctxt "name" -msgid "Mirror Tool" +msgid "STL Reader" msgstr "" msgctxt "description" -msgid "Provides the Scale tool." +msgid "Makes it possible to write Wavefront OBJ files." msgstr "" msgctxt "name" -msgid "Scale Tool" +msgid "Wavefront OBJ Writer" msgstr "" msgctxt "description" -msgid "Provides the Selection tool." +msgid "Outputs log information to the console." msgstr "" msgctxt "name" -msgid "Selection Tool" +msgid "Console Logger" msgstr "" msgctxt "description" -msgid "Enables saving to local files." +msgid "Outputs log information to a file in your settings folder." msgstr "" msgctxt "name" -msgid "Local File Output Device" +msgid "File Logger" msgstr "" msgctxt "description" -msgid "Outputs log information to a file in your settings folder." +msgid "Provides a simple solid mesh view." msgstr "" msgctxt "name" -msgid "File Logger" +msgid "Simple View" msgstr "" msgctxt "description" -msgid "Outputs log information to the console." +msgid "Checks for updates of the software." msgstr "" msgctxt "name" -msgid "Console Logger" +msgid "Update Checker" msgstr "" msgctxt "description" -msgid "Checks for updates of the software." +msgid "Provides the Mirror tool." msgstr "" msgctxt "name" -msgid "Update Checker" +msgid "Mirror Tool" msgstr "" msgctxt "description" -msgid "Makes it possible to read Wavefront OBJ files." +msgid "Provides the Move tool." msgstr "" msgctxt "name" -msgid "Wavefront OBJ Reader" +msgid "Move Tool" msgstr "" msgctxt "description" -msgid "Makes it possible to write Wavefront OBJ files." +msgid "Provides the Rotate tool." msgstr "" msgctxt "name" -msgid "Wavefront OBJ Writer" +msgid "Rotate Tool" msgstr "" msgctxt "description" -msgid "Provides support for reading STL files." +msgid "Provides the tool to manipulate the camera." msgstr "" msgctxt "name" -msgid "STL Reader" +msgid "Camera Tool" msgstr "" msgctxt "description" -msgid "Provides support for writing STL files." +msgid "Provides the Scale tool." msgstr "" msgctxt "name" -msgid "STL Writer" +msgid "Scale Tool" msgstr "" msgctxt "description" -msgid "Provides a simple solid mesh view." +msgid "Provides the Selection tool." msgstr "" msgctxt "name" -msgid "Simple View" +msgid "Selection Tool" msgstr "" diff --git a/resources/i18n/zh_CN/uranium.po b/resources/i18n/zh_CN/uranium.po index 7034120b14..5a6f379d34 100644 --- a/resources/i18n/zh_CN/uranium.po +++ b/resources/i18n/zh_CN/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-07 08:39+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/zh_TW/uranium.po b/resources/i18n/zh_TW/uranium.po index 54e1781295..335ec87c9a 100644 --- a/resources/i18n/zh_TW/uranium.po +++ b/resources/i18n/zh_TW/uranium.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Uranium 5.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-07 08:39+0000\n" "PO-Revision-Date: 2022-01-02 20:28+0800\n" "Last-Translator: Valen Chang \n" "Language-Team: Valen Chang \n" From addcd3f18e9e886f9b9363c65287363af6fec8c5 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 7 Jul 2023 10:39:17 +0200 Subject: [PATCH 06/45] Clean up formatting of errors in populate metadata Boyscouting CURA-10717 --- UM/PluginRegistry.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/UM/PluginRegistry.py b/UM/PluginRegistry.py index 94103917e3..137424a632 100644 --- a/UM/PluginRegistry.py +++ b/UM/PluginRegistry.py @@ -898,20 +898,20 @@ def _populateMetaData(self, plugin_id: str) -> bool: with open(metadata_file, "r", encoding = "utf-8") as file_stream: self._parsePluginInfo(plugin_id, file_stream.read(), meta_data) except FileNotFoundError: - Logger.logException("e", "Unable to find the required plugin.json file for plugin %s", plugin_id) + Logger.logException("e", f"Unable to find the required plugin.json file for plugin {plugin_id}") raise InvalidMetaDataError(plugin_id) except UnicodeDecodeError: - Logger.logException("e", f"The plug-in metadata file for plug-in {plugin_id} is corrupt.".format(plugin_id = plugin_id)) + Logger.logException("e", f"The plug-in metadata file for plug-in {plugin_id} is corrupt.") raise InvalidMetaDataError(plugin_id) except EnvironmentError as e: - Logger.logException("e", "Can't open the metadata file for plug-in {plugin_id}: {err}".format(plugin_id = plugin_id, err = str(e))) + Logger.logException("e", "Can't open the metadata file for plug-in {plugin_id}") raise InvalidMetaDataError(plugin_id) except AttributeError as e: - Logger.log("e", "Plug-in {plugin_id} has no getMetaData function to get metadata of the plug-in: {err}".format(plugin_id = plugin_id, err = str(e))) + Logger.logException("e", f"Plug-in {plugin_id} has no getMetaData function to get metadata of the plug-in") raise InvalidMetaDataError(plugin_id) except TypeError as e: - Logger.log("e", "Plug-in {plugin_id} has a getMetaData function with the wrong signature: {err}".format(plugin_id = plugin_id, err = str(e))) + Logger.logException("e", f"Plug-in {plugin_id} has a getMetaData function with the wrong signature") raise InvalidMetaDataError(plugin_id) if not meta_data: From ba3016bcbe6546d3f8389af493bf523e75bcd72f Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 7 Jul 2023 10:41:59 +0200 Subject: [PATCH 07/45] Add missing typing Boyscouting CURA-10717 --- UM/PluginRegistry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UM/PluginRegistry.py b/UM/PluginRegistry.py index 137424a632..944b84d959 100644 --- a/UM/PluginRegistry.py +++ b/UM/PluginRegistry.py @@ -667,7 +667,7 @@ def _getPluginIdFromFile(filename: str) -> Optional[str]: return None # Signals that loading this failed. return plugin_id - def _findInstalledPlugins(self, paths = None) -> List[str]: + def _findInstalledPlugins(self, paths: Optional[List[str]] = None) -> List[str]: """ Returns a list of all possible plugin ids in the plugin locations :param paths: @@ -721,7 +721,7 @@ def _findPlugin(self, plugin_id: str) -> Optional[types.ModuleType]: highest_version = Version(0) for loc in locations: - meta_data: Dict[str, Any] = {} + meta_data: Dict[str, Any] = {} plugin_location = os.path.join(loc, plugin_id) metadata_file = os.path.join(plugin_location, "plugin.json") try: From 8653d6ff2a5483fcf6699e34e6f7aa67b76d5e62 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 7 Jul 2023 10:59:08 +0200 Subject: [PATCH 08/45] Add BackendPlugin stub CURA-10717 --- UM/Application.py | 8 ++++++++ UM/BackendPlugin.py | 13 +++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 UM/BackendPlugin.py diff --git a/UM/Application.py b/UM/Application.py index 9434558725..61662192ad 100644 --- a/UM/Application.py +++ b/UM/Application.py @@ -34,6 +34,7 @@ from UM.Backend.Backend import Backend from UM.Settings.ContainerStack import ContainerStack from UM.Extension import Extension + from UM.BackendPlugin import BackendPlugin @signalemitter @@ -198,6 +199,7 @@ def initialize(self) -> None: i18nCatalog.setApplication(self) PluginRegistry.addType("backend", self.setBackend) + PluginRegistry.addType("backend_plugin", self.addBackendPlugin) PluginRegistry.addType("logger", Logger.addLogger) PluginRegistry.addType("extension", self.addExtension) PluginRegistry.addType("file_provider", self.addFileProvider) @@ -479,6 +481,12 @@ def addExtension(self, extension: "Extension") -> None: def getExtensions(self) -> List["Extension"]: return self._extensions + def addBackendPlugin(self, backend_plugin: "BackendPlugin") -> None: + self._backend_plugins.append(backend_plugin) + + def getBackendPlugins(self) -> List["BackendPlugin"]: + return self._backend_plugins + def addFileProvider(self, file_provider: "FileProvider") -> None: self._file_providers.append(file_provider) diff --git a/UM/BackendPlugin.py b/UM/BackendPlugin.py new file mode 100644 index 0000000000..ae936640b3 --- /dev/null +++ b/UM/BackendPlugin.py @@ -0,0 +1,13 @@ +# Copyright (c) 2023 Ultimaker B.V. +# Uranium is released under the terms of the LGPLv3 or higher. + + +from UM.PluginObject import PluginObject +import collections +from typing import Optional, Any, Callable, List, Dict + + +class BackendPlugin(PluginObject): + + def __init__(self) -> None: + super().__init__() \ No newline at end of file From d24e0348bd6c6f8b201f69f800c40d026d5927cd Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 7 Jul 2023 11:11:28 +0200 Subject: [PATCH 09/45] Add missing typing for backend Boyscouting CURA-10717 --- UM/Backend/Backend.py | 54 +++++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/UM/Backend/Backend.py b/UM/Backend/Backend.py index c0aa2fb8ca..e68870dac2 100644 --- a/UM/Backend/Backend.py +++ b/UM/Backend/Backend.py @@ -2,12 +2,11 @@ # Uranium is released under the terms of the LGPLv3 or higher. from enum import IntEnum -import struct import subprocess import sys import threading from time import sleep -from typing import Any, Dict, Optional +from typing import Any, Dict, Optional, List from UM.Backend.SignalSocket import SignalSocket from UM.Logger import Logger @@ -36,7 +35,7 @@ class Backend(PluginObject): The message_handlers dict should be filled with string (full name of proto message), function pairs. """ - def __init__(self): + def __init__(self) -> None: super().__init__() # Call super to make multiple inheritance work. self._supported_commands = {} @@ -44,8 +43,8 @@ def __init__(self): self._socket = None self._port = 49674 - self._process = None # type: Optional[subprocess.Popen] - self._backend_log = [] + self._process: Optional[subprocess.Popen] = None + self._backend_log: List[bytes] = [] self._backend_log_max_lines = None self._backend_state = BackendState.NotStarted @@ -58,7 +57,7 @@ def __init__(self): backendQuit = Signal() backendDone = Signal() - def setState(self, new_state): + def setState(self, new_state: BackendState) -> None: if new_state != self._backend_state: self._backend_state = new_state self.backendStateChange.emit(self._backend_state) @@ -66,9 +65,9 @@ def setState(self, new_state): if self._backend_state == BackendState.Done: self.backendDone.emit() - - def startEngine(self): - """:brief Start the backend / engine. + def startEngine(self) -> None: + """ + Start the backend / engine. Runs the engine, this is only called when the socket is fully opened & ready to accept connections """ @@ -101,21 +100,22 @@ def startEngine(self): t.daemon = True t.start() - def close(self): + def close(self) -> None: if self._socket: while self._socket.getState() == Arcus.SocketState.Opening: sleep(0.1) self._socket.close() - def _backendLog(self, line): + def _backendLog(self, line: bytes) -> None: try: line_str = line.decode("utf-8") except UnicodeDecodeError: - line_str = line.decode("latin1") #Latin-1 as a fallback since it can never give decoding errors. All characters are 1 byte. + # We use Latin-1 as a fallback since it can never give decoding errors. All characters are 1 byte. + line_str = line.decode("latin1") Logger.log("d", "[Backend] " + line_str.strip()) self._backend_log.append(line) - def getLog(self): + def getLog(self) -> List[bytes]: """Get the logging messages of the backend connection.""" if self._backend_log_max_lines and type(self._backend_log_max_lines) == int: @@ -123,15 +123,19 @@ def getLog(self): del(self._backend_log[0]) return self._backend_log - def getEngineCommand(self): + def getEngineCommand(self) -> List[str]: """Get the command used to start the backend executable """ return [UM.Application.Application.getInstance().getPreferences().getValue("backend/location"), "--port", str(self._socket.getPort())] - def _runEngineProcess(self, command_list) -> Optional[subprocess.Popen]: - """Start the (external) backend process.""" + def _runEngineProcess(self, command_list: List[str]) -> Optional[subprocess.Popen]: + """ + Start the (external) backend process. + :param command_list: + :return: + """ - kwargs = {} #type: Dict[str, Any] + kwargs: Dict[str, Any] = {} if sys.platform == "win32": su = subprocess.STARTUPINFO() su.dwFlags |= subprocess.STARTF_USESHOWWINDOW @@ -174,7 +178,7 @@ def _storeStderrToLogThread(self, handle): break self._backendLog(line) - def _onSocketStateChanged(self, state): + def _onSocketStateChanged(self, state: Arcus.SocketState) -> None: """Private socket state changed handler.""" self._logSocketState(state) @@ -185,7 +189,7 @@ def _onSocketStateChanged(self, state): Logger.log("d", "Backend connected on port %s", self._port) self.backendConnected.emit() - def _logSocketState(self, state): + def _logSocketState(self, state: Arcus.SocketState) -> None: """Debug function created to provide more info for CURA-2127""" if state == Arcus.SocketState.Listening: @@ -201,8 +205,8 @@ def _logSocketState(self, state): elif state == Arcus.SocketState.Closed: Logger.log("d", "Socket state changed to Closed") - def _onMessageReceived(self): - """Private message handler""" + def _onMessageReceived(self) -> None: + """Protected message handler""" message = self._socket.takeNextMessage() @@ -212,7 +216,7 @@ def _onMessageReceived(self): self._message_handlers[message.getTypeName()](message) - def _onSocketError(self, error): + def _onSocketError(self, error: Arcus.ErrorCode) -> None: """Private socket error handler""" if error.getErrorCode() == Arcus.ErrorCode.BindFailedError: @@ -228,9 +232,13 @@ def _onSocketError(self, error): self._createSocket() - def _createSocket(self, protocol_file): + def _createSocket(self, protocol_file: Optional[str] = None) -> None: """Creates a socket and attaches listeners.""" + if not protocol_file: + Logger.log("w", "Unable to create socket without protocol file!") + return + if self._socket: Logger.log("d", "Previous socket existed. Closing that first.") # temp debug logging self._socket.stateChanged.disconnect(self._onSocketStateChanged) From 8125f54ae26d2a838ec134a96684d7ce7b3a8ddc Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 7 Jul 2023 11:15:59 +0200 Subject: [PATCH 10/45] Fix more typing And more boyscouting. Damn this is some code that hasn't been touched in a while... CURA-10717 --- UM/Backend/Backend.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/UM/Backend/Backend.py b/UM/Backend/Backend.py index e68870dac2..67726e756b 100644 --- a/UM/Backend/Backend.py +++ b/UM/Backend/Backend.py @@ -6,7 +6,7 @@ import sys import threading from time import sleep -from typing import Any, Dict, Optional, List +from typing import Any, Dict, Optional, List, Callable from UM.Backend.SignalSocket import SignalSocket from UM.Logger import Logger @@ -36,10 +36,9 @@ class Backend(PluginObject): """ def __init__(self) -> None: - super().__init__() # Call super to make multiple inheritance work. - self._supported_commands = {} + super().__init__() - self._message_handlers = {} + self._message_handlers: Dict[str, Callable[Arcus.PythonMessage]] = {} self._socket = None self._port = 49674 @@ -47,7 +46,7 @@ def __init__(self) -> None: self._backend_log: List[bytes] = [] self._backend_log_max_lines = None - self._backend_state = BackendState.NotStarted + self._backend_state: BackendState = BackendState.NotStarted UM.Application.Application.getInstance().callLater(self._createSocket) From b9b80d30eb6c5ad31ac9d6223ed58e754d6ae33a Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 7 Jul 2023 11:34:51 +0200 Subject: [PATCH 11/45] Move decode line behavior into it's own function Boyscouting CURA-10717 --- UM/Backend/Backend.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/UM/Backend/Backend.py b/UM/Backend/Backend.py index 67726e756b..e371f4ef10 100644 --- a/UM/Backend/Backend.py +++ b/UM/Backend/Backend.py @@ -105,12 +105,16 @@ def close(self) -> None: sleep(0.1) self._socket.close() - def _backendLog(self, line: bytes) -> None: + @staticmethod + def _decodeLine(line: bytes) -> str: try: - line_str = line.decode("utf-8") + return line.decode("utf-8") except UnicodeDecodeError: - # We use Latin-1 as a fallback since it can never give decoding errors. All characters are 1 byte. - line_str = line.decode("latin1") + # We use Latin-1 as a fallback since it can never give decoding errors. All characters are 1 byte + return line.decode("latin1") + + def _backendLog(self, line: bytes) -> None: + line_str = self._decodeLine(line) Logger.log("d", "[Backend] " + line_str.strip()) self._backend_log.append(line) From e6e922df56d03677b36b9af087ae6edbcc425627 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 7 Jul 2023 11:47:10 +0200 Subject: [PATCH 12/45] Split up code into seperate functions CURA-10717 --- UM/Backend/Backend.py | 57 ++++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/UM/Backend/Backend.py b/UM/Backend/Backend.py index e371f4ef10..4dacee3ddd 100644 --- a/UM/Backend/Backend.py +++ b/UM/Backend/Backend.py @@ -75,30 +75,40 @@ def startEngine(self) -> None: self._createSocket() return - if not self._backend_log_max_lines: - self._backend_log = [] - - # Double check that the old process is indeed killed. - if self._process is not None: - try: - self._process.terminate() - except PermissionError: - Logger.log("e", "Unable to kill running engine. Access is denied.") - return - Logger.log("d", "Engine process is killed. Received return code %s", self._process.wait()) + self._flushBackendLog() + self._ensureOldProcessIsTerminated() self._process = self._runEngineProcess(command) if self._process is None: # Failed to start engine. return Logger.log("i", "Started engine process: %s", self.getEngineCommand()[0]) + + self._beginThreads() + + def _beginThreads(self) -> None: self._backendLog(bytes("Calling engine with: %s\n" % self.getEngineCommand(), "utf-8")) - t = threading.Thread(target = self._storeOutputToLogThread, args = (self._process.stdout,), name = "EngineOutputThread") + t = threading.Thread(target=self._storeOutputToLogThread, args=(self._process.stdout,), + name="EngineOutputThread") t.daemon = True t.start() - t = threading.Thread(target = self._storeStderrToLogThread, args = (self._process.stderr,), name = "EngineErrorThread") + t = threading.Thread(target=self._storeStderrToLogThread, args=(self._process.stderr,), + name="EngineErrorThread") t.daemon = True t.start() + def _ensureOldProcessIsTerminated(self) -> None: + if self._process is not None: + try: + self._process.terminate() + except PermissionError: + Logger.log("e", "Unable to kill running engine. Access is denied.") + return + Logger.log("d", "Engine process is killed. Received return code %s", self._process.wait()) + + def _flushBackendLog(self) -> None: + if not self._backend_log_max_lines: + self._backend_log = [] + def close(self) -> None: if self._socket: while self._socket.getState() == Arcus.SocketState.Opening: @@ -235,6 +245,17 @@ def _onSocketError(self, error: Arcus.ErrorCode) -> None: self._createSocket() + def _cleanupExistingSocket(self) -> None: + self._socket.stateChanged.disconnect(self._onSocketStateChanged) + self._socket.messageReceived.disconnect(self._onMessageReceived) + self._socket.error.disconnect(self._onSocketError) + # Hack for (at least) Linux. If the socket is connecting, the close will deadlock. + while self._socket.getState() == Arcus.SocketState.Opening: + sleep(0.1) + # If the error occurred due to parsing, both connections believe that connection is okay. + # So we need to force a close. + self._socket.close() + def _createSocket(self, protocol_file: Optional[str] = None) -> None: """Creates a socket and attaches listeners.""" @@ -244,15 +265,7 @@ def _createSocket(self, protocol_file: Optional[str] = None) -> None: if self._socket: Logger.log("d", "Previous socket existed. Closing that first.") # temp debug logging - self._socket.stateChanged.disconnect(self._onSocketStateChanged) - self._socket.messageReceived.disconnect(self._onMessageReceived) - self._socket.error.disconnect(self._onSocketError) - # Hack for (at least) Linux. If the socket is connecting, the close will deadlock. - while self._socket.getState() == Arcus.SocketState.Opening: - sleep(0.1) - # If the error occurred due to parsing, both connections believe that connection is okay. - # So we need to force a close. - self._socket.close() + self._cleanupExistingSocket() self._socket = SignalSocket() self._socket.stateChanged.connect(self._onSocketStateChanged) From 88474236dcd5876521508ecc3ad2d79d9c78f9a6 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 7 Jul 2023 11:57:03 +0200 Subject: [PATCH 13/45] Add missing documentation CURA-10717 --- UM/Backend/Backend.py | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/UM/Backend/Backend.py b/UM/Backend/Backend.py index 4dacee3ddd..7c2a43a66f 100644 --- a/UM/Backend/Backend.py +++ b/UM/Backend/Backend.py @@ -19,7 +19,18 @@ class BackendState(IntEnum): - """The current processing state of the backend.""" + """ + The current processing state of the backend. + + :class:`BackendState` is an enumeration class that represents the different states that the backend can be in. + + Attributes: + - NotStarted (int): The backend has not started processing. + - Processing (int): The backend is currently processing data. + - Done (int): The backend has finished processing successfully. + - Error (int): The backend encountered an error during processing. + - Disabled (int): The backend is disabled and cannot process data. + """ NotStarted = 1 Processing = 2 @@ -30,7 +41,8 @@ class BackendState(IntEnum): @signalemitter class Backend(PluginObject): - """Base class for any backend communication (separate piece of software). + """ + Base class for any backend communication (separate piece of software). It makes use of the Socket class from libArcus for the actual communication bits. The message_handlers dict should be filled with string (full name of proto message), function pairs. """ @@ -129,8 +141,11 @@ def _backendLog(self, line: bytes) -> None: self._backend_log.append(line) def getLog(self) -> List[bytes]: - """Get the logging messages of the backend connection.""" + """ + Returns the backend log. + :return: A list of bytes representing the backend log. + """ if self._backend_log_max_lines and type(self._backend_log_max_lines) == int: while len(self._backend_log) >= self._backend_log_max_lines: del(self._backend_log[0]) @@ -181,6 +196,14 @@ def _storeOutputToLogThread(self, handle): self._backendLog(line) def _storeStderrToLogThread(self, handle): + """ + Stores the standard error output from the backend process to the log. + + :param handle: The handle to the standard error output stream. + :type handle: file-like object + + :return: None + """ while True: try: line = handle.readline() @@ -257,8 +280,12 @@ def _cleanupExistingSocket(self) -> None: self._socket.close() def _createSocket(self, protocol_file: Optional[str] = None) -> None: - """Creates a socket and attaches listeners.""" + """ + Create a socket for communication with an external backend. + :param protocol_file: Optional. The path to the protocol file. Default is None. + :return: None + """ if not protocol_file: Logger.log("w", "Unable to create socket without protocol file!") return From 0dc96517e9e737df1b641e44ac644374245500ff Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 7 Jul 2023 16:33:04 +0200 Subject: [PATCH 14/45] Add start & stop functions to plugin_backend Pretty rudimentary stuff, but one needs plumbing before moving on CURA-10717 --- UM/BackendPlugin.py | 64 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/UM/BackendPlugin.py b/UM/BackendPlugin.py index ae936640b3..f54669cdc7 100644 --- a/UM/BackendPlugin.py +++ b/UM/BackendPlugin.py @@ -1,13 +1,67 @@ # Copyright (c) 2023 Ultimaker B.V. # Uranium is released under the terms of the LGPLv3 or higher. +import subprocess +from typing import Optional, List - +from UM.Logger import Logger from UM.PluginObject import PluginObject -import collections -from typing import Optional, Any, Callable, List, Dict class BackendPlugin(PluginObject): - def __init__(self) -> None: - super().__init__() \ No newline at end of file + super().__init__() + self.__port: int = 0 + self._plugin_address: str = "127.0.0.1" + self._plugin_command: Optional[List[str]] = None + self._process = None + + def setPort(self, port: int) -> None: + self.__port = port + + def getPort(self) -> int: + return self.__port + + def _validatePluginCommand(self) -> list[str]: + """ + Validate the plugin command and add the port parameter if it is missing. + + :return: A list of strings containing the validated plugin command. + """ + if not self._plugin_command or "--port" in self._plugin_command: + return self._plugin_command or [] + + return self._plugin_command + ["--port", str(self.__port)] + + def start(self) -> bool: + """ + Starts the backend_plugin process. + + :return: True if the plugin process started successfully, False otherwise. + """ + try: + # STDIN needs to be None because we provide no input, but communicate via a local socket instead. + # The NUL device sometimes doesn't exist on some computers. + self._process = subprocess.Popen(self._validatePluginCommand(), stdin = None) + return True + except PermissionError: + Logger.log("e", f"Couldn't start backend_plugin [{self._plugin_id}]: No permission to execute process.") + except FileNotFoundError: + Logger.logException("e", f"Unable to find backend_plugin executable [{self._plugin_id}]") + except BlockingIOError: + Logger.logException("e", f"Couldn't start backend_plugin [{self._plugin_id}]: Resource is temporarily unavailable") + except OSError as e: + Logger.logException("e", f"Couldn't start backend_plugin [{self._plugin_id}]: Operating system is blocking it (antivirus?)") + return False + + def stop(self) -> bool: + if not self._process: + return True # Nothing to stop + + try: + self._process.terminate() + return_code = self._process.wait() + Logger.log("d", f"Backend_plugin [{self._plugin_id}] was killed. Received return code {return_code}") + return True + except PermissionError: + Logger.log("e", "Unable to kill running engine. Access is denied.") + return False From bfebc9a68ff7345d1dc3e0de33e25beb4ffd403f Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 10 Jul 2023 11:46:37 +0200 Subject: [PATCH 15/45] Add function to ensure that backend plugins can be started CURA-10717 --- UM/Backend/Backend.py | 15 +++++++++++++++ UM/BackendPlugin.py | 7 +++++++ 2 files changed, 22 insertions(+) diff --git a/UM/Backend/Backend.py b/UM/Backend/Backend.py index 7c2a43a66f..923d578438 100644 --- a/UM/Backend/Backend.py +++ b/UM/Backend/Backend.py @@ -54,6 +54,7 @@ def __init__(self) -> None: self._socket = None self._port = 49674 + self._last_backend_plugin_port = self._port + 1000 self._process: Optional[subprocess.Popen] = None self._backend_log: List[bytes] = [] self._backend_log_max_lines = None @@ -76,6 +77,20 @@ def setState(self, new_state: BackendState) -> None: if self._backend_state == BackendState.Done: self.backendDone.emit() + def startPlugins(self) -> None: + """ + Ensure that all backend plugins are started + :return: + """ + backend_plugins = UM.Application.Application.getInstance().getBackendPlugins() + for backend_plugin in backend_plugins: + if backend_plugin.isRunning(): + continue + # Set the port to prevent plugins from using the same one. + backend_plugin.setPort(self._last_backend_plugin_port) + self.__last_backend_plugin_port += 1 + backend_plugin.start() + def startEngine(self) -> None: """ Start the backend / engine. diff --git a/UM/BackendPlugin.py b/UM/BackendPlugin.py index f54669cdc7..b76ef7fe56 100644 --- a/UM/BackendPlugin.py +++ b/UM/BackendPlugin.py @@ -14,6 +14,10 @@ def __init__(self) -> None: self._plugin_address: str = "127.0.0.1" self._plugin_command: Optional[List[str]] = None self._process = None + self._is_running = False + + def isRunning(self): + return self._is_running def setPort(self, port: int) -> None: self.__port = port @@ -42,6 +46,7 @@ def start(self) -> bool: # STDIN needs to be None because we provide no input, but communicate via a local socket instead. # The NUL device sometimes doesn't exist on some computers. self._process = subprocess.Popen(self._validatePluginCommand(), stdin = None) + self._is_running = True return True except PermissionError: Logger.log("e", f"Couldn't start backend_plugin [{self._plugin_id}]: No permission to execute process.") @@ -55,11 +60,13 @@ def start(self) -> bool: def stop(self) -> bool: if not self._process: + self._is_running = False return True # Nothing to stop try: self._process.terminate() return_code = self._process.wait() + self._is_running = False Logger.log("d", f"Backend_plugin [{self._plugin_id}] was killed. Received return code {return_code}") return True except PermissionError: From c22f7294b97be334fe857e1e965c697aca73da1f Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 11 Jul 2023 11:25:40 +0200 Subject: [PATCH 16/45] Move backend plugin logic to Cura from Uranium I was running into abstraction issues when it was defined in Uranium. Instead of trying to fight those, it's just easier to move it to Cura CURA-10717 --- UM/Application.py | 8 ----- UM/Backend/Backend.py | 15 --------- UM/BackendPlugin.py | 74 ------------------------------------------- 3 files changed, 97 deletions(-) delete mode 100644 UM/BackendPlugin.py diff --git a/UM/Application.py b/UM/Application.py index 61662192ad..9434558725 100644 --- a/UM/Application.py +++ b/UM/Application.py @@ -34,7 +34,6 @@ from UM.Backend.Backend import Backend from UM.Settings.ContainerStack import ContainerStack from UM.Extension import Extension - from UM.BackendPlugin import BackendPlugin @signalemitter @@ -199,7 +198,6 @@ def initialize(self) -> None: i18nCatalog.setApplication(self) PluginRegistry.addType("backend", self.setBackend) - PluginRegistry.addType("backend_plugin", self.addBackendPlugin) PluginRegistry.addType("logger", Logger.addLogger) PluginRegistry.addType("extension", self.addExtension) PluginRegistry.addType("file_provider", self.addFileProvider) @@ -481,12 +479,6 @@ def addExtension(self, extension: "Extension") -> None: def getExtensions(self) -> List["Extension"]: return self._extensions - def addBackendPlugin(self, backend_plugin: "BackendPlugin") -> None: - self._backend_plugins.append(backend_plugin) - - def getBackendPlugins(self) -> List["BackendPlugin"]: - return self._backend_plugins - def addFileProvider(self, file_provider: "FileProvider") -> None: self._file_providers.append(file_provider) diff --git a/UM/Backend/Backend.py b/UM/Backend/Backend.py index 923d578438..7c2a43a66f 100644 --- a/UM/Backend/Backend.py +++ b/UM/Backend/Backend.py @@ -54,7 +54,6 @@ def __init__(self) -> None: self._socket = None self._port = 49674 - self._last_backend_plugin_port = self._port + 1000 self._process: Optional[subprocess.Popen] = None self._backend_log: List[bytes] = [] self._backend_log_max_lines = None @@ -77,20 +76,6 @@ def setState(self, new_state: BackendState) -> None: if self._backend_state == BackendState.Done: self.backendDone.emit() - def startPlugins(self) -> None: - """ - Ensure that all backend plugins are started - :return: - """ - backend_plugins = UM.Application.Application.getInstance().getBackendPlugins() - for backend_plugin in backend_plugins: - if backend_plugin.isRunning(): - continue - # Set the port to prevent plugins from using the same one. - backend_plugin.setPort(self._last_backend_plugin_port) - self.__last_backend_plugin_port += 1 - backend_plugin.start() - def startEngine(self) -> None: """ Start the backend / engine. diff --git a/UM/BackendPlugin.py b/UM/BackendPlugin.py deleted file mode 100644 index b76ef7fe56..0000000000 --- a/UM/BackendPlugin.py +++ /dev/null @@ -1,74 +0,0 @@ -# Copyright (c) 2023 Ultimaker B.V. -# Uranium is released under the terms of the LGPLv3 or higher. -import subprocess -from typing import Optional, List - -from UM.Logger import Logger -from UM.PluginObject import PluginObject - - -class BackendPlugin(PluginObject): - def __init__(self) -> None: - super().__init__() - self.__port: int = 0 - self._plugin_address: str = "127.0.0.1" - self._plugin_command: Optional[List[str]] = None - self._process = None - self._is_running = False - - def isRunning(self): - return self._is_running - - def setPort(self, port: int) -> None: - self.__port = port - - def getPort(self) -> int: - return self.__port - - def _validatePluginCommand(self) -> list[str]: - """ - Validate the plugin command and add the port parameter if it is missing. - - :return: A list of strings containing the validated plugin command. - """ - if not self._plugin_command or "--port" in self._plugin_command: - return self._plugin_command or [] - - return self._plugin_command + ["--port", str(self.__port)] - - def start(self) -> bool: - """ - Starts the backend_plugin process. - - :return: True if the plugin process started successfully, False otherwise. - """ - try: - # STDIN needs to be None because we provide no input, but communicate via a local socket instead. - # The NUL device sometimes doesn't exist on some computers. - self._process = subprocess.Popen(self._validatePluginCommand(), stdin = None) - self._is_running = True - return True - except PermissionError: - Logger.log("e", f"Couldn't start backend_plugin [{self._plugin_id}]: No permission to execute process.") - except FileNotFoundError: - Logger.logException("e", f"Unable to find backend_plugin executable [{self._plugin_id}]") - except BlockingIOError: - Logger.logException("e", f"Couldn't start backend_plugin [{self._plugin_id}]: Resource is temporarily unavailable") - except OSError as e: - Logger.logException("e", f"Couldn't start backend_plugin [{self._plugin_id}]: Operating system is blocking it (antivirus?)") - return False - - def stop(self) -> bool: - if not self._process: - self._is_running = False - return True # Nothing to stop - - try: - self._process.terminate() - return_code = self._process.wait() - self._is_running = False - Logger.log("d", f"Backend_plugin [{self._plugin_id}] was killed. Received return code {return_code}") - return True - except PermissionError: - Logger.log("e", "Unable to kill running engine. Access is denied.") - return False From 722d4d895313c23b517d70d02d7d759ea18e2813 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 12 Jul 2023 10:49:53 +0200 Subject: [PATCH 17/45] Use logger.warn and logger.error instead of generic .log As per suggestions of review CURA-10717 Co-authored-by: Jelle Spijker --- UM/Backend/Backend.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UM/Backend/Backend.py b/UM/Backend/Backend.py index 7c2a43a66f..709ea5e633 100644 --- a/UM/Backend/Backend.py +++ b/UM/Backend/Backend.py @@ -113,7 +113,7 @@ def _ensureOldProcessIsTerminated(self) -> None: try: self._process.terminate() except PermissionError: - Logger.log("e", "Unable to kill running engine. Access is denied.") + Logger.error("Unable to kill running engine. Access is denied.") return Logger.log("d", "Engine process is killed. Received return code %s", self._process.wait()) @@ -287,7 +287,7 @@ def _createSocket(self, protocol_file: Optional[str] = None) -> None: :return: None """ if not protocol_file: - Logger.log("w", "Unable to create socket without protocol file!") + Logger.warn("Unable to create socket without protocol file!") return if self._socket: From 0466c7128f0cc8dc0b8ccdf0a2b62a1914a4cc97 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 12 Jul 2023 10:50:45 +0200 Subject: [PATCH 18/45] Add missing typing Apply suggestions from code review CURA-10717 Co-authored-by: Jelle Spijker --- UM/Backend/Backend.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UM/Backend/Backend.py b/UM/Backend/Backend.py index 709ea5e633..fb878233bb 100644 --- a/UM/Backend/Backend.py +++ b/UM/Backend/Backend.py @@ -6,7 +6,7 @@ import sys import threading from time import sleep -from typing import Any, Dict, Optional, List, Callable +from typing import Any, Dict, Optional, List, Callable, TextIO from UM.Backend.SignalSocket import SignalSocket from UM.Logger import Logger @@ -195,7 +195,7 @@ def _storeOutputToLogThread(self, handle): break self._backendLog(line) - def _storeStderrToLogThread(self, handle): + def _storeStderrToLogThread(self, handle: TextIO) -> None: """ Stores the standard error output from the backend process to the log. From 54663a4d3606aa26f9a5084a345a2e83ca1b1e82 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Tue, 18 Jul 2023 11:56:42 +0200 Subject: [PATCH 19/45] Update conanfile.py --- conanfile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/conanfile.py b/conanfile.py index 0288041094..3cd2a3a495 100644 --- a/conanfile.py +++ b/conanfile.py @@ -159,3 +159,4 @@ def package_id(self): self.info.clear() del self.info.options.devtools + From 9db5ff57798ed91b90e87a38165d74baf869d98a Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Tue, 18 Jul 2023 12:20:43 +0200 Subject: [PATCH 20/45] Use pyarcus from branch CURA-10475 --- conanfile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conanfile.py b/conanfile.py index 3cd2a3a495..5737a638f7 100644 --- a/conanfile.py +++ b/conanfile.py @@ -34,7 +34,7 @@ class UraniumConan(ConanFile): def set_version(self): if not self.version: - self.version = "5.4.0-beta.1" + self.version = "5.5.0-alpha" @property def _base_dir(self): @@ -95,7 +95,7 @@ def validate(self): raise ConanInvalidConfiguration("Only versions 5+ are support") def requirements(self): - self.requires("pyarcus/5.2.2") + self.requires("pyarcus/latest@ultimaker/cura_10475") self.requires("cpython/3.10.4") def build_requirements(self): From ccd4c6530146fe2cfe74b0037e95de72d2d7c0f2 Mon Sep 17 00:00:00 2001 From: jellespijker Date: Tue, 18 Jul 2023 10:27:04 +0000 Subject: [PATCH 21/45] update translations --- resources/i18n/cs_CZ/uranium.po | 2 +- resources/i18n/de_DE/uranium.po | 2 +- resources/i18n/es_ES/uranium.po | 2 +- resources/i18n/fi_FI/uranium.po | 2 +- resources/i18n/fr_FR/uranium.po | 2 +- resources/i18n/hu_HU/uranium.po | 2 +- resources/i18n/it_IT/uranium.po | 2 +- resources/i18n/ja_JP/uranium.po | 2 +- resources/i18n/ko_KR/uranium.po | 2 +- resources/i18n/nl_NL/uranium.po | 2 +- resources/i18n/pl_PL/uranium.po | 2 +- resources/i18n/pt_BR/uranium.po | 2 +- resources/i18n/pt_PT/uranium.po | 2 +- resources/i18n/ru_RU/uranium.po | 2 +- resources/i18n/tr_TR/uranium.po | 2 +- resources/i18n/uranium.pot | 42 ++++++++++++++++----------------- resources/i18n/zh_CN/uranium.po | 2 +- resources/i18n/zh_TW/uranium.po | 2 +- 18 files changed, 38 insertions(+), 38 deletions(-) diff --git a/resources/i18n/cs_CZ/uranium.po b/resources/i18n/cs_CZ/uranium.po index 239eefd6f8..a4e5469a8f 100644 --- a/resources/i18n/cs_CZ/uranium.po +++ b/resources/i18n/cs_CZ/uranium.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Uranium 5.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-07 08:39+0000\n" +"POT-Creation-Date: 2023-07-18 10:27+0000\n" "PO-Revision-Date: 2022-04-26 00:14+0200\n" "Last-Translator: Miroslav Šustek \n" "Language-Team: DenyCZ \n" diff --git a/resources/i18n/de_DE/uranium.po b/resources/i18n/de_DE/uranium.po index dbc278811d..2adb083d87 100644 --- a/resources/i18n/de_DE/uranium.po +++ b/resources/i18n/de_DE/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-07 08:39+0000\n" +"POT-Creation-Date: 2023-07-18 10:27+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/es_ES/uranium.po b/resources/i18n/es_ES/uranium.po index 06d2a302c2..f6e0908e11 100644 --- a/resources/i18n/es_ES/uranium.po +++ b/resources/i18n/es_ES/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-07 08:39+0000\n" +"POT-Creation-Date: 2023-07-18 10:27+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/fi_FI/uranium.po b/resources/i18n/fi_FI/uranium.po index 2acd3c740b..1450ce37f1 100644 --- a/resources/i18n/fi_FI/uranium.po +++ b/resources/i18n/fi_FI/uranium.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Uranium 5.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-07 08:39+0000\n" +"POT-Creation-Date: 2023-07-18 10:27+0000\n" "PO-Revision-Date: 2017-09-27 12:27+0200\n" "Last-Translator: Bothof \n" "Language-Team: Finnish \n" diff --git a/resources/i18n/fr_FR/uranium.po b/resources/i18n/fr_FR/uranium.po index 273208f782..f126e75552 100644 --- a/resources/i18n/fr_FR/uranium.po +++ b/resources/i18n/fr_FR/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-07 08:39+0000\n" +"POT-Creation-Date: 2023-07-18 10:27+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/hu_HU/uranium.po b/resources/i18n/hu_HU/uranium.po index 7aed4e8211..0a5edc6a90 100644 --- a/resources/i18n/hu_HU/uranium.po +++ b/resources/i18n/hu_HU/uranium.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Uranium 5.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-07 08:39+0000\n" +"POT-Creation-Date: 2023-07-18 10:27+0000\n" "PO-Revision-Date: 2019-10-10 22:30+0200\n" "Last-Translator: Nagy Attila\n" "Language-Team: AT-VLOG \n" diff --git a/resources/i18n/it_IT/uranium.po b/resources/i18n/it_IT/uranium.po index 3f00151427..23bfda86d0 100644 --- a/resources/i18n/it_IT/uranium.po +++ b/resources/i18n/it_IT/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-07 08:39+0000\n" +"POT-Creation-Date: 2023-07-18 10:27+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/ja_JP/uranium.po b/resources/i18n/ja_JP/uranium.po index 92bea5b94e..4939b64683 100644 --- a/resources/i18n/ja_JP/uranium.po +++ b/resources/i18n/ja_JP/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-07 08:39+0000\n" +"POT-Creation-Date: 2023-07-18 10:27+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/ko_KR/uranium.po b/resources/i18n/ko_KR/uranium.po index 3d77e44325..68277d8098 100644 --- a/resources/i18n/ko_KR/uranium.po +++ b/resources/i18n/ko_KR/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-07 08:39+0000\n" +"POT-Creation-Date: 2023-07-18 10:27+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/nl_NL/uranium.po b/resources/i18n/nl_NL/uranium.po index 57bf080da6..b42657f552 100644 --- a/resources/i18n/nl_NL/uranium.po +++ b/resources/i18n/nl_NL/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-07 08:39+0000\n" +"POT-Creation-Date: 2023-07-18 10:27+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/pl_PL/uranium.po b/resources/i18n/pl_PL/uranium.po index 61b28043c3..23eb003363 100644 --- a/resources/i18n/pl_PL/uranium.po +++ b/resources/i18n/pl_PL/uranium.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Uranium 5.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-07 08:39+0000\n" +"POT-Creation-Date: 2023-07-18 10:27+0000\n" "PO-Revision-Date: 2019-11-15 15:35+0100\n" "Last-Translator: Mariusz 'Virgin71' Matłosz \n" "Language-Team: reprapy.pl\n" diff --git a/resources/i18n/pt_BR/uranium.po b/resources/i18n/pt_BR/uranium.po index 97b9f35d3f..7f7cb3550b 100644 --- a/resources/i18n/pt_BR/uranium.po +++ b/resources/i18n/pt_BR/uranium.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Uranium 5.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-07 08:39+0000\n" +"POT-Creation-Date: 2023-07-18 10:27+0000\n" "PO-Revision-Date: 2022-04-25 07:03+0200\n" "Last-Translator: Cláudio Sampaio \n" "Language-Team: Cláudio Sampaio\n" diff --git a/resources/i18n/pt_PT/uranium.po b/resources/i18n/pt_PT/uranium.po index 035d0d5332..0d09af71c2 100644 --- a/resources/i18n/pt_PT/uranium.po +++ b/resources/i18n/pt_PT/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-07 08:39+0000\n" +"POT-Creation-Date: 2023-07-18 10:27+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/ru_RU/uranium.po b/resources/i18n/ru_RU/uranium.po index 872741cfb7..9017602f28 100644 --- a/resources/i18n/ru_RU/uranium.po +++ b/resources/i18n/ru_RU/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-07 08:39+0000\n" +"POT-Creation-Date: 2023-07-18 10:27+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/tr_TR/uranium.po b/resources/i18n/tr_TR/uranium.po index a96cab9a74..9aee929c72 100644 --- a/resources/i18n/tr_TR/uranium.po +++ b/resources/i18n/tr_TR/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-07 08:39+0000\n" +"POT-Creation-Date: 2023-07-18 10:27+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/uranium.pot b/resources/i18n/uranium.pot index ed5fbc8cac..e4dcc36f73 100644 --- a/resources/i18n/uranium.pot +++ b/resources/i18n/uranium.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-07 08:39+0000\n" +"POT-Creation-Date: 2023-07-18 10:27+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -590,19 +590,11 @@ msgctxt "@info:status" msgid "{application_name} {version_number} provides a better and more reliable printing experience." msgstr "" msgctxt "description" -msgid "Provides built-in setting containers that come with the installation of the application." -msgstr "" - -msgctxt "name" -msgid "Local Container Provider" -msgstr "" - -msgctxt "description" -msgid "Enables saving to local files." +msgid "Outputs log information to the console." msgstr "" msgctxt "name" -msgid "Local File Output Device" +msgid "Console Logger" msgstr "" msgctxt "description" @@ -622,27 +614,27 @@ msgid "STL Writer" msgstr "" msgctxt "description" -msgid "Provides support for reading STL files." +msgid "Makes it possible to write Wavefront OBJ files." msgstr "" msgctxt "name" -msgid "STL Reader" +msgid "Wavefront OBJ Writer" msgstr "" msgctxt "description" -msgid "Makes it possible to write Wavefront OBJ files." +msgid "Provides support for reading STL files." msgstr "" msgctxt "name" -msgid "Wavefront OBJ Writer" +msgid "STL Reader" msgstr "" msgctxt "description" -msgid "Outputs log information to the console." +msgid "Provides built-in setting containers that come with the installation of the application." msgstr "" msgctxt "name" -msgid "Console Logger" +msgid "Local Container Provider" msgstr "" msgctxt "description" @@ -661,6 +653,14 @@ msgctxt "name" msgid "Simple View" msgstr "" +msgctxt "description" +msgid "Enables saving to local files." +msgstr "" + +msgctxt "name" +msgid "Local File Output Device" +msgstr "" + msgctxt "description" msgid "Checks for updates of the software." msgstr "" @@ -678,11 +678,11 @@ msgid "Mirror Tool" msgstr "" msgctxt "description" -msgid "Provides the Move tool." +msgid "Provides the Scale tool." msgstr "" msgctxt "name" -msgid "Move Tool" +msgid "Scale Tool" msgstr "" msgctxt "description" @@ -702,11 +702,11 @@ msgid "Camera Tool" msgstr "" msgctxt "description" -msgid "Provides the Scale tool." +msgid "Provides the Move tool." msgstr "" msgctxt "name" -msgid "Scale Tool" +msgid "Move Tool" msgstr "" msgctxt "description" diff --git a/resources/i18n/zh_CN/uranium.po b/resources/i18n/zh_CN/uranium.po index 5a6f379d34..0a98da1be8 100644 --- a/resources/i18n/zh_CN/uranium.po +++ b/resources/i18n/zh_CN/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-07 08:39+0000\n" +"POT-Creation-Date: 2023-07-18 10:27+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/zh_TW/uranium.po b/resources/i18n/zh_TW/uranium.po index 335ec87c9a..69cd9d1228 100644 --- a/resources/i18n/zh_TW/uranium.po +++ b/resources/i18n/zh_TW/uranium.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Uranium 5.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-07 08:39+0000\n" +"POT-Creation-Date: 2023-07-18 10:27+0000\n" "PO-Revision-Date: 2022-01-02 20:28+0800\n" "Last-Translator: Valen Chang \n" "Language-Team: Valen Chang \n" From 461d3e444866162ed9f41c8d7224ce00ffadcb42 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Tue, 18 Jul 2023 12:29:49 +0200 Subject: [PATCH 22/45] Update conanfile.py --- conanfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conanfile.py b/conanfile.py index 5737a638f7..e0aa286bac 100644 --- a/conanfile.py +++ b/conanfile.py @@ -95,7 +95,7 @@ def validate(self): raise ConanInvalidConfiguration("Only versions 5+ are support") def requirements(self): - self.requires("pyarcus/latest@ultimaker/cura_10475") + self.requires("pyarcus/(latest)@ultimaker/cura_10475") self.requires("cpython/3.10.4") def build_requirements(self): From d77f1ef83d1afb9c6d719bf2429cda7cfd4f024a Mon Sep 17 00:00:00 2001 From: jellespijker Date: Tue, 18 Jul 2023 10:33:02 +0000 Subject: [PATCH 23/45] update translations --- resources/i18n/cs_CZ/uranium.po | 2 +- resources/i18n/de_DE/uranium.po | 2 +- resources/i18n/es_ES/uranium.po | 2 +- resources/i18n/fi_FI/uranium.po | 2 +- resources/i18n/fr_FR/uranium.po | 2 +- resources/i18n/hu_HU/uranium.po | 2 +- resources/i18n/it_IT/uranium.po | 2 +- resources/i18n/ja_JP/uranium.po | 2 +- resources/i18n/ko_KR/uranium.po | 2 +- resources/i18n/nl_NL/uranium.po | 2 +- resources/i18n/pl_PL/uranium.po | 2 +- resources/i18n/pt_BR/uranium.po | 2 +- resources/i18n/pt_PT/uranium.po | 2 +- resources/i18n/ru_RU/uranium.po | 2 +- resources/i18n/tr_TR/uranium.po | 2 +- resources/i18n/uranium.pot | 42 ++++++++++++++++----------------- resources/i18n/zh_CN/uranium.po | 2 +- resources/i18n/zh_TW/uranium.po | 2 +- 18 files changed, 38 insertions(+), 38 deletions(-) diff --git a/resources/i18n/cs_CZ/uranium.po b/resources/i18n/cs_CZ/uranium.po index a4e5469a8f..eeb187198e 100644 --- a/resources/i18n/cs_CZ/uranium.po +++ b/resources/i18n/cs_CZ/uranium.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Uranium 5.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-18 10:27+0000\n" +"POT-Creation-Date: 2023-07-18 10:33+0000\n" "PO-Revision-Date: 2022-04-26 00:14+0200\n" "Last-Translator: Miroslav Šustek \n" "Language-Team: DenyCZ \n" diff --git a/resources/i18n/de_DE/uranium.po b/resources/i18n/de_DE/uranium.po index 2adb083d87..801338722b 100644 --- a/resources/i18n/de_DE/uranium.po +++ b/resources/i18n/de_DE/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-18 10:27+0000\n" +"POT-Creation-Date: 2023-07-18 10:33+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/es_ES/uranium.po b/resources/i18n/es_ES/uranium.po index f6e0908e11..5ab31de959 100644 --- a/resources/i18n/es_ES/uranium.po +++ b/resources/i18n/es_ES/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-18 10:27+0000\n" +"POT-Creation-Date: 2023-07-18 10:33+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/fi_FI/uranium.po b/resources/i18n/fi_FI/uranium.po index 1450ce37f1..5c52f9366b 100644 --- a/resources/i18n/fi_FI/uranium.po +++ b/resources/i18n/fi_FI/uranium.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Uranium 5.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-18 10:27+0000\n" +"POT-Creation-Date: 2023-07-18 10:33+0000\n" "PO-Revision-Date: 2017-09-27 12:27+0200\n" "Last-Translator: Bothof \n" "Language-Team: Finnish \n" diff --git a/resources/i18n/fr_FR/uranium.po b/resources/i18n/fr_FR/uranium.po index f126e75552..e55c03e8c7 100644 --- a/resources/i18n/fr_FR/uranium.po +++ b/resources/i18n/fr_FR/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-18 10:27+0000\n" +"POT-Creation-Date: 2023-07-18 10:33+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/hu_HU/uranium.po b/resources/i18n/hu_HU/uranium.po index 0a5edc6a90..19ca5811ec 100644 --- a/resources/i18n/hu_HU/uranium.po +++ b/resources/i18n/hu_HU/uranium.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Uranium 5.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-18 10:27+0000\n" +"POT-Creation-Date: 2023-07-18 10:33+0000\n" "PO-Revision-Date: 2019-10-10 22:30+0200\n" "Last-Translator: Nagy Attila\n" "Language-Team: AT-VLOG \n" diff --git a/resources/i18n/it_IT/uranium.po b/resources/i18n/it_IT/uranium.po index 23bfda86d0..702f03f278 100644 --- a/resources/i18n/it_IT/uranium.po +++ b/resources/i18n/it_IT/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-18 10:27+0000\n" +"POT-Creation-Date: 2023-07-18 10:33+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/ja_JP/uranium.po b/resources/i18n/ja_JP/uranium.po index 4939b64683..b618751051 100644 --- a/resources/i18n/ja_JP/uranium.po +++ b/resources/i18n/ja_JP/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-18 10:27+0000\n" +"POT-Creation-Date: 2023-07-18 10:33+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/ko_KR/uranium.po b/resources/i18n/ko_KR/uranium.po index 68277d8098..79085b9b56 100644 --- a/resources/i18n/ko_KR/uranium.po +++ b/resources/i18n/ko_KR/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-18 10:27+0000\n" +"POT-Creation-Date: 2023-07-18 10:33+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/nl_NL/uranium.po b/resources/i18n/nl_NL/uranium.po index b42657f552..37f6f390c7 100644 --- a/resources/i18n/nl_NL/uranium.po +++ b/resources/i18n/nl_NL/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-18 10:27+0000\n" +"POT-Creation-Date: 2023-07-18 10:33+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/pl_PL/uranium.po b/resources/i18n/pl_PL/uranium.po index 23eb003363..35f1973ebf 100644 --- a/resources/i18n/pl_PL/uranium.po +++ b/resources/i18n/pl_PL/uranium.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Uranium 5.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-18 10:27+0000\n" +"POT-Creation-Date: 2023-07-18 10:33+0000\n" "PO-Revision-Date: 2019-11-15 15:35+0100\n" "Last-Translator: Mariusz 'Virgin71' Matłosz \n" "Language-Team: reprapy.pl\n" diff --git a/resources/i18n/pt_BR/uranium.po b/resources/i18n/pt_BR/uranium.po index 7f7cb3550b..67755e1d37 100644 --- a/resources/i18n/pt_BR/uranium.po +++ b/resources/i18n/pt_BR/uranium.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Uranium 5.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-18 10:27+0000\n" +"POT-Creation-Date: 2023-07-18 10:33+0000\n" "PO-Revision-Date: 2022-04-25 07:03+0200\n" "Last-Translator: Cláudio Sampaio \n" "Language-Team: Cláudio Sampaio\n" diff --git a/resources/i18n/pt_PT/uranium.po b/resources/i18n/pt_PT/uranium.po index 0d09af71c2..1ebef6dd4a 100644 --- a/resources/i18n/pt_PT/uranium.po +++ b/resources/i18n/pt_PT/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-18 10:27+0000\n" +"POT-Creation-Date: 2023-07-18 10:33+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/ru_RU/uranium.po b/resources/i18n/ru_RU/uranium.po index 9017602f28..c7c36a726b 100644 --- a/resources/i18n/ru_RU/uranium.po +++ b/resources/i18n/ru_RU/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-18 10:27+0000\n" +"POT-Creation-Date: 2023-07-18 10:33+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/tr_TR/uranium.po b/resources/i18n/tr_TR/uranium.po index 9aee929c72..ece428e975 100644 --- a/resources/i18n/tr_TR/uranium.po +++ b/resources/i18n/tr_TR/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-18 10:27+0000\n" +"POT-Creation-Date: 2023-07-18 10:33+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/uranium.pot b/resources/i18n/uranium.pot index e4dcc36f73..4745fc6657 100644 --- a/resources/i18n/uranium.pot +++ b/resources/i18n/uranium.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-18 10:27+0000\n" +"POT-Creation-Date: 2023-07-18 10:33+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -590,35 +590,35 @@ msgctxt "@info:status" msgid "{application_name} {version_number} provides a better and more reliable printing experience." msgstr "" msgctxt "description" -msgid "Outputs log information to the console." +msgid "Provides built-in setting containers that come with the installation of the application." msgstr "" msgctxt "name" -msgid "Console Logger" +msgid "Local Container Provider" msgstr "" msgctxt "description" -msgid "Makes it possible to read Wavefront OBJ files." +msgid "Enables saving to local files." msgstr "" msgctxt "name" -msgid "Wavefront OBJ Reader" +msgid "Local File Output Device" msgstr "" msgctxt "description" -msgid "Provides support for writing STL files." +msgid "Makes it possible to read Wavefront OBJ files." msgstr "" msgctxt "name" -msgid "STL Writer" +msgid "Wavefront OBJ Reader" msgstr "" msgctxt "description" -msgid "Makes it possible to write Wavefront OBJ files." +msgid "Provides support for writing STL files." msgstr "" msgctxt "name" -msgid "Wavefront OBJ Writer" +msgid "STL Writer" msgstr "" msgctxt "description" @@ -630,35 +630,35 @@ msgid "STL Reader" msgstr "" msgctxt "description" -msgid "Provides built-in setting containers that come with the installation of the application." +msgid "Makes it possible to write Wavefront OBJ files." msgstr "" msgctxt "name" -msgid "Local Container Provider" +msgid "Wavefront OBJ Writer" msgstr "" msgctxt "description" -msgid "Outputs log information to a file in your settings folder." +msgid "Outputs log information to the console." msgstr "" msgctxt "name" -msgid "File Logger" +msgid "Console Logger" msgstr "" msgctxt "description" -msgid "Provides a simple solid mesh view." +msgid "Outputs log information to a file in your settings folder." msgstr "" msgctxt "name" -msgid "Simple View" +msgid "File Logger" msgstr "" msgctxt "description" -msgid "Enables saving to local files." +msgid "Provides a simple solid mesh view." msgstr "" msgctxt "name" -msgid "Local File Output Device" +msgid "Simple View" msgstr "" msgctxt "description" @@ -678,11 +678,11 @@ msgid "Mirror Tool" msgstr "" msgctxt "description" -msgid "Provides the Scale tool." +msgid "Provides the Move tool." msgstr "" msgctxt "name" -msgid "Scale Tool" +msgid "Move Tool" msgstr "" msgctxt "description" @@ -702,11 +702,11 @@ msgid "Camera Tool" msgstr "" msgctxt "description" -msgid "Provides the Move tool." +msgid "Provides the Scale tool." msgstr "" msgctxt "name" -msgid "Move Tool" +msgid "Scale Tool" msgstr "" msgctxt "description" diff --git a/resources/i18n/zh_CN/uranium.po b/resources/i18n/zh_CN/uranium.po index 0a98da1be8..a1e51cb9a2 100644 --- a/resources/i18n/zh_CN/uranium.po +++ b/resources/i18n/zh_CN/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-18 10:27+0000\n" +"POT-Creation-Date: 2023-07-18 10:33+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/zh_TW/uranium.po b/resources/i18n/zh_TW/uranium.po index 69cd9d1228..d0cea84670 100644 --- a/resources/i18n/zh_TW/uranium.po +++ b/resources/i18n/zh_TW/uranium.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Uranium 5.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-18 10:27+0000\n" +"POT-Creation-Date: 2023-07-18 10:33+0000\n" "PO-Revision-Date: 2022-01-02 20:28+0800\n" "Last-Translator: Valen Chang \n" "Language-Team: Valen Chang \n" From 7c80d929722041b7e1674ea3eaf3c289a8fd7376 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Mon, 24 Jul 2023 21:16:03 +0200 Subject: [PATCH 24/45] Change CURA-10676 --- UM/Settings/SettingDefinition.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/UM/Settings/SettingDefinition.py b/UM/Settings/SettingDefinition.py index 371617ffd7..8d3c6367f0 100644 --- a/UM/Settings/SettingDefinition.py +++ b/UM/Settings/SettingDefinition.py @@ -105,8 +105,8 @@ def __init__(self, key: str, container: Optional[DefinitionContainerInterface] = self._i18n_catalog = i18n_catalog # type: Optional[i18nCatalog] - self._children = [] # type: List[SettingDefinition] - self._relations = [] # type: List[SettingRelation] + self._children = [] # type: List[SettingDefinition] + self._relations = [] # type: List[SettingRelation] # Cached set of keys of ancestors. Used for fast lookups of ancestors. self.__ancestors = set() # type: Set[str] @@ -116,6 +116,17 @@ def __init__(self, key: str, container: Optional[DefinitionContainerInterface] = self.__property_values = {} # type: Dict[str, Any] + def extend_category(self, value_id: str, value_display: str, plugin_id: Optional[str] = None) -> None: + """Append a category to the setting. + + :param value_id: :type{str} The id of the category. + :param value_display: :type{str} The display name of the category. If the display string needs to be translated, provide the translated string. + :param plugin_id: :type{Optional[str]} The id of the plugin that owns the category. Defaults to None. + """ + + value_id = f"{plugin_id}::{value_id}" if plugin_id else value_id + self.options[value_id] = value_display + def __getattr__(self, name: str) -> Any: """Override __getattr__ to provide access to definition properties.""" From 08327e384f64a1158de84aedc8091492f04c03bb Mon Sep 17 00:00:00 2001 From: casperlamboo Date: Mon, 24 Jul 2023 19:29:44 +0000 Subject: [PATCH 25/45] update translations --- resources/i18n/cs_CZ/uranium.po | 2 +- resources/i18n/de_DE/uranium.po | 2 +- resources/i18n/es_ES/uranium.po | 2 +- resources/i18n/fi_FI/uranium.po | 2 +- resources/i18n/fr_FR/uranium.po | 2 +- resources/i18n/hu_HU/uranium.po | 2 +- resources/i18n/it_IT/uranium.po | 2 +- resources/i18n/ja_JP/uranium.po | 2 +- resources/i18n/ko_KR/uranium.po | 2 +- resources/i18n/nl_NL/uranium.po | 2 +- resources/i18n/pl_PL/uranium.po | 2 +- resources/i18n/pt_BR/uranium.po | 2 +- resources/i18n/pt_PT/uranium.po | 2 +- resources/i18n/ru_RU/uranium.po | 2 +- resources/i18n/tr_TR/uranium.po | 2 +- resources/i18n/uranium.pot | 62 ++++++++++++++++----------------- resources/i18n/zh_CN/uranium.po | 2 +- resources/i18n/zh_TW/uranium.po | 2 +- 18 files changed, 48 insertions(+), 48 deletions(-) diff --git a/resources/i18n/cs_CZ/uranium.po b/resources/i18n/cs_CZ/uranium.po index c42b1f2519..02df201e4f 100644 --- a/resources/i18n/cs_CZ/uranium.po +++ b/resources/i18n/cs_CZ/uranium.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Uranium 5.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-24 19:29+0000\n" "PO-Revision-Date: 2022-04-26 00:14+0200\n" "Last-Translator: Miroslav Šustek \n" "Language-Team: DenyCZ \n" diff --git a/resources/i18n/de_DE/uranium.po b/resources/i18n/de_DE/uranium.po index d40e9fda6d..f21d069830 100644 --- a/resources/i18n/de_DE/uranium.po +++ b/resources/i18n/de_DE/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-24 19:29+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/es_ES/uranium.po b/resources/i18n/es_ES/uranium.po index ce699b1c17..6cde5a2e88 100644 --- a/resources/i18n/es_ES/uranium.po +++ b/resources/i18n/es_ES/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-24 19:29+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/fi_FI/uranium.po b/resources/i18n/fi_FI/uranium.po index 2536199cbb..3e456e084d 100644 --- a/resources/i18n/fi_FI/uranium.po +++ b/resources/i18n/fi_FI/uranium.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Uranium 5.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-24 19:29+0000\n" "PO-Revision-Date: 2017-09-27 12:27+0200\n" "Last-Translator: Bothof \n" "Language-Team: Finnish \n" diff --git a/resources/i18n/fr_FR/uranium.po b/resources/i18n/fr_FR/uranium.po index 862b24f052..3d0355b3a1 100644 --- a/resources/i18n/fr_FR/uranium.po +++ b/resources/i18n/fr_FR/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-24 19:29+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/hu_HU/uranium.po b/resources/i18n/hu_HU/uranium.po index 10f6f721e5..2f654cd4b1 100644 --- a/resources/i18n/hu_HU/uranium.po +++ b/resources/i18n/hu_HU/uranium.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Uranium 5.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-24 19:29+0000\n" "PO-Revision-Date: 2019-10-10 22:30+0200\n" "Last-Translator: Nagy Attila\n" "Language-Team: AT-VLOG \n" diff --git a/resources/i18n/it_IT/uranium.po b/resources/i18n/it_IT/uranium.po index 5a2858b9e6..49b18bb8ae 100644 --- a/resources/i18n/it_IT/uranium.po +++ b/resources/i18n/it_IT/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-24 19:29+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/ja_JP/uranium.po b/resources/i18n/ja_JP/uranium.po index f627239f3e..63fc986411 100644 --- a/resources/i18n/ja_JP/uranium.po +++ b/resources/i18n/ja_JP/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-24 19:29+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/ko_KR/uranium.po b/resources/i18n/ko_KR/uranium.po index f50fe99714..0752d7f29c 100644 --- a/resources/i18n/ko_KR/uranium.po +++ b/resources/i18n/ko_KR/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-24 19:29+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/nl_NL/uranium.po b/resources/i18n/nl_NL/uranium.po index cb7b8ce870..4a6669dd99 100644 --- a/resources/i18n/nl_NL/uranium.po +++ b/resources/i18n/nl_NL/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-24 19:29+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/pl_PL/uranium.po b/resources/i18n/pl_PL/uranium.po index bb88e3ed21..2b971906ae 100644 --- a/resources/i18n/pl_PL/uranium.po +++ b/resources/i18n/pl_PL/uranium.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Uranium 5.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-24 19:29+0000\n" "PO-Revision-Date: 2019-11-15 15:35+0100\n" "Last-Translator: Mariusz 'Virgin71' Matłosz \n" "Language-Team: reprapy.pl\n" diff --git a/resources/i18n/pt_BR/uranium.po b/resources/i18n/pt_BR/uranium.po index 5c07d56c42..cbbcdf8941 100644 --- a/resources/i18n/pt_BR/uranium.po +++ b/resources/i18n/pt_BR/uranium.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Uranium 5.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-24 19:29+0000\n" "PO-Revision-Date: 2022-04-25 07:03+0200\n" "Last-Translator: Cláudio Sampaio \n" "Language-Team: Cláudio Sampaio\n" diff --git a/resources/i18n/pt_PT/uranium.po b/resources/i18n/pt_PT/uranium.po index 57630d4313..70b01512d8 100644 --- a/resources/i18n/pt_PT/uranium.po +++ b/resources/i18n/pt_PT/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-24 19:29+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/ru_RU/uranium.po b/resources/i18n/ru_RU/uranium.po index 1df9c05df3..7fd47727dd 100644 --- a/resources/i18n/ru_RU/uranium.po +++ b/resources/i18n/ru_RU/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-24 19:29+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/tr_TR/uranium.po b/resources/i18n/tr_TR/uranium.po index 8ecca11761..bc8435b215 100644 --- a/resources/i18n/tr_TR/uranium.po +++ b/resources/i18n/tr_TR/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-24 19:29+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/uranium.pot b/resources/i18n/uranium.pot index fcee1f415f..c4871cdbd0 100644 --- a/resources/i18n/uranium.pot +++ b/resources/i18n/uranium.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-24 19:29+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -598,122 +598,122 @@ msgid "Local Container Provider" msgstr "" msgctxt "description" -msgid "Provides the Rotate tool." +msgid "Enables saving to local files." msgstr "" msgctxt "name" -msgid "Rotate Tool" +msgid "Local File Output Device" msgstr "" msgctxt "description" -msgid "Provides the Move tool." +msgid "Makes it possible to read Wavefront OBJ files." msgstr "" msgctxt "name" -msgid "Move Tool" +msgid "Wavefront OBJ Reader" msgstr "" msgctxt "description" -msgid "Provides the tool to manipulate the camera." +msgid "Provides support for writing STL files." msgstr "" msgctxt "name" -msgid "Camera Tool" +msgid "STL Writer" msgstr "" msgctxt "description" -msgid "Provides the Mirror tool." +msgid "Provides support for reading STL files." msgstr "" msgctxt "name" -msgid "Mirror Tool" +msgid "STL Reader" msgstr "" msgctxt "description" -msgid "Provides the Scale tool." +msgid "Makes it possible to write Wavefront OBJ files." msgstr "" msgctxt "name" -msgid "Scale Tool" +msgid "Wavefront OBJ Writer" msgstr "" msgctxt "description" -msgid "Provides the Selection tool." +msgid "Outputs log information to the console." msgstr "" msgctxt "name" -msgid "Selection Tool" +msgid "Console Logger" msgstr "" msgctxt "description" -msgid "Enables saving to local files." +msgid "Outputs log information to a file in your settings folder." msgstr "" msgctxt "name" -msgid "Local File Output Device" +msgid "File Logger" msgstr "" msgctxt "description" -msgid "Outputs log information to a file in your settings folder." +msgid "Provides a simple solid mesh view." msgstr "" msgctxt "name" -msgid "File Logger" +msgid "Simple View" msgstr "" msgctxt "description" -msgid "Outputs log information to the console." +msgid "Checks for updates of the software." msgstr "" msgctxt "name" -msgid "Console Logger" +msgid "Update Checker" msgstr "" msgctxt "description" -msgid "Checks for updates of the software." +msgid "Provides the Mirror tool." msgstr "" msgctxt "name" -msgid "Update Checker" +msgid "Mirror Tool" msgstr "" msgctxt "description" -msgid "Makes it possible to read Wavefront OBJ files." +msgid "Provides the Move tool." msgstr "" msgctxt "name" -msgid "Wavefront OBJ Reader" +msgid "Move Tool" msgstr "" msgctxt "description" -msgid "Makes it possible to write Wavefront OBJ files." +msgid "Provides the Rotate tool." msgstr "" msgctxt "name" -msgid "Wavefront OBJ Writer" +msgid "Rotate Tool" msgstr "" msgctxt "description" -msgid "Provides support for reading STL files." +msgid "Provides the tool to manipulate the camera." msgstr "" msgctxt "name" -msgid "STL Reader" +msgid "Camera Tool" msgstr "" msgctxt "description" -msgid "Provides support for writing STL files." +msgid "Provides the Scale tool." msgstr "" msgctxt "name" -msgid "STL Writer" +msgid "Scale Tool" msgstr "" msgctxt "description" -msgid "Provides a simple solid mesh view." +msgid "Provides the Selection tool." msgstr "" msgctxt "name" -msgid "Simple View" +msgid "Selection Tool" msgstr "" diff --git a/resources/i18n/zh_CN/uranium.po b/resources/i18n/zh_CN/uranium.po index 7034120b14..9e1ec5c995 100644 --- a/resources/i18n/zh_CN/uranium.po +++ b/resources/i18n/zh_CN/uranium.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-24 19:29+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/resources/i18n/zh_TW/uranium.po b/resources/i18n/zh_TW/uranium.po index 54e1781295..8b97673860 100644 --- a/resources/i18n/zh_TW/uranium.po +++ b/resources/i18n/zh_TW/uranium.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Uranium 5.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-03 10:52+0000\n" +"POT-Creation-Date: 2023-07-24 19:29+0000\n" "PO-Revision-Date: 2022-01-02 20:28+0800\n" "Last-Translator: Valen Chang \n" "Language-Team: Valen Chang \n" From 9acb2fa12b2a1d682c54a20092827f15f36e6c47 Mon Sep 17 00:00:00 2001 From: Casper Lamboo Date: Tue, 25 Jul 2023 09:25:24 +0200 Subject: [PATCH 26/45] Use idoimatic python variable definitions CURA-10720 Co-authored-by: Jaime van Kessel --- UM/Settings/SettingDefinition.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UM/Settings/SettingDefinition.py b/UM/Settings/SettingDefinition.py index 8d3c6367f0..bc904aca46 100644 --- a/UM/Settings/SettingDefinition.py +++ b/UM/Settings/SettingDefinition.py @@ -105,8 +105,8 @@ def __init__(self, key: str, container: Optional[DefinitionContainerInterface] = self._i18n_catalog = i18n_catalog # type: Optional[i18nCatalog] - self._children = [] # type: List[SettingDefinition] - self._relations = [] # type: List[SettingRelation] + self._children: List[SettingDefinition] = [] + self._relations: List[SettingRelation] = [] # Cached set of keys of ancestors. Used for fast lookups of ancestors. self.__ancestors = set() # type: Set[str] From e48fc281e5682db107e52a0a3ac2d9df23da4352 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Tue, 25 Jul 2023 14:46:33 +0200 Subject: [PATCH 27/45] Add plugin version to custom enum values CURA-10720 --- UM/Settings/SettingDefinition.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/UM/Settings/SettingDefinition.py b/UM/Settings/SettingDefinition.py index bc904aca46..2249496107 100644 --- a/UM/Settings/SettingDefinition.py +++ b/UM/Settings/SettingDefinition.py @@ -116,15 +116,19 @@ def __init__(self, key: str, container: Optional[DefinitionContainerInterface] = self.__property_values = {} # type: Dict[str, Any] - def extend_category(self, value_id: str, value_display: str, plugin_id: Optional[str] = None) -> None: + def extend_category(self, value_id: str, value_display: str, plugin_id: Optional[str] = None, + plugin_version: Optional[str] = None) -> None: """Append a category to the setting. :param value_id: :type{str} The id of the category. :param value_display: :type{str} The display name of the category. If the display string needs to be translated, provide the translated string. :param plugin_id: :type{Optional[str]} The id of the plugin that owns the category. Defaults to None. + :param plugin_version: :type{Optional[str]} The version of the plugin that owns the category. Defaults to None. """ - - value_id = f"{plugin_id}::{value_id}" if plugin_id else value_id + if plugin_id is not None and plugin_version is not None: + value_id = f"PLUGIN::{plugin_id}@{plugin_version}::{value_id}" + elif plugin_id is not None or plugin_version is not None: + raise ValueError("Both plugin_id and plugin_version must be provided if one of them is provided.") self.options[value_id] = value_display def __getattr__(self, name: str) -> Any: From 23d4c8ab8e024af4d84ff688d9c9bb8a63a2f23e Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 26 Jul 2023 07:05:24 +0200 Subject: [PATCH 28/45] Add a proper way for plugins to add settings. Since they're loaded on top instead of going through the normal inheritance system, we need to merge/add them to each definition-container. This can't happen during load from file either, since most times, it's loaded from the cache-db instead. Instead, load the extra settings into the Definition ones each time one is loaded. If this turns out to be slow, we could pre-parse the json into (proto) SettingsDefinitions beforehand maybe, but it's not clear how much that is the actual bottleneck and how much code would need to be either duplicated or upended. part of CURA-10722 --- .../AdditionalSettingDefinitionAppender.py | 19 ++++++++ UM/Settings/ContainerRegistry.py | 15 ++++++ UM/Settings/DefinitionContainer.py | 47 +++++++++++++++++-- 3 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 UM/Settings/AdditionalSettingDefinitionAppender.py diff --git a/UM/Settings/AdditionalSettingDefinitionAppender.py b/UM/Settings/AdditionalSettingDefinitionAppender.py new file mode 100644 index 0000000000..29e5971166 --- /dev/null +++ b/UM/Settings/AdditionalSettingDefinitionAppender.py @@ -0,0 +1,19 @@ +# Copyright (c) 2023 UltiMaker. +# Uranium is released under the terms of the LGPLv3 or higher. + +from typing import Any, Dict +from UM.PluginObject import PluginObject + + +class AdditionalSettingDefinitionsAppender(PluginObject): + + def __init__(self) -> None: + super().__init__() + + def getAdditionalSettingDefinitions(self) -> Dict[str, Dict[str, Any]]: + """ + Should return the settings added by this plugin in json format. + The settings should be divided by category (either existing or new ones). + Settings in existing categories will be appended, new categories will be created. + """ + raise NotImplementedError("A setting_definitions_appender needs to implement getAdditionalSettingDefinitions.") diff --git a/UM/Settings/ContainerRegistry.py b/UM/Settings/ContainerRegistry.py index 1d7d1254a4..b477e321ea 100644 --- a/UM/Settings/ContainerRegistry.py +++ b/UM/Settings/ContainerRegistry.py @@ -17,6 +17,7 @@ from UM.Settings.EmptyInstanceContainer import EmptyInstanceContainer from UM.Settings.ContainerFormatError import ContainerFormatError from UM.Settings.ContainerProvider import ContainerProvider +from UM.Settings.AdditionalSettingDefinitionAppender import AdditionalSettingDefinitionsAppender from UM.Settings.constant_instance_containers import empty_container from . import ContainerQuery from UM.Settings.ContainerStack import ContainerStack @@ -59,6 +60,9 @@ def __init__(self, application: "QtApplication") -> None: self._providers = [] # type: List[ContainerProvider] PluginRegistry.addType("container_provider", self.addProvider) + self._additional_setting_definitions_list: List[Dict[str, Dict[str, Any]]] = [] + PluginRegistry.addType("setting_definitions_appender", self.addAdditionalSettingDefinitionsAppender) + self.metadata = {} # type: Dict[str, metadata_type] self._containers = {} # type: Dict[str, ContainerInterface] self._wrong_container_ids = set() # type: Set[str] # Set of already known wrong containers that must be skipped @@ -115,6 +119,11 @@ def addProvider(self, provider: ContainerProvider) -> None: # Re-sort every time. It's quadratic, but there shouldn't be that many providers anyway... self._providers.sort(key = lambda provider: PluginRegistry.getInstance().getMetaData(provider.getPluginId())["container_provider"].get("priority", 0)) + def addAdditionalSettingDefinitionsAppender(self, appender: AdditionalSettingDefinitionsAppender) -> None: + """Adds a provider for additional setting definitions to append to each definition-container.""" + + self._additional_setting_definitions_list.append(appender.getAdditionalSettingDefinitions()) + def findDefinitionContainers(self, **kwargs: Any) -> List[DefinitionContainerInterface]: """Find all DefinitionContainer objects matching certain criteria. @@ -607,6 +616,12 @@ def addContainer(self, container: ContainerInterface) -> bool: self.source_provider[container_id] = None # Added during runtime. self._clearQueryCacheByContainer(container) + for additional_setting_definitions in self._additional_setting_definitions_list: + if container.getMetaDataEntry("type") == "extruder" or not isinstance(container, DefinitionContainer): + continue + container = cast(DefinitionContainer, container) + container.appendAdditionalSettingDefinitions(additional_setting_definitions) + # containerAdded is a custom signal and can trigger direct calls to its subscribers. This should be avoided # because with the direct calls, the subscribers need to know everything about what it tries to do to avoid # triggering this signal again, which eventually can end up exceeding the max recursion limit. diff --git a/UM/Settings/DefinitionContainer.py b/UM/Settings/DefinitionContainer.py index bbcbe9e7fd..c41039b06a 100644 --- a/UM/Settings/DefinitionContainer.py +++ b/UM/Settings/DefinitionContainer.py @@ -332,16 +332,54 @@ def deserialize(self, serialized: str, file_name: Optional[str] = None) -> str: self._metadata["version"] = self.Version #Guaranteed to be equal to what's in the parsed data by the validation. self._metadata["container_type"] = DefinitionContainer - for key, value in parsed["settings"].items(): - definition = SettingDefinition(key, self, None, self._i18n_catalog) + self._deserializeDefinitions(parsed["settings"]) + return serialized + + def _deserializeDefinitions(self, settings_dict: Dict[str, Any], force_category: Optional[str] = None) -> None: + + # When there is a forced category (= parent) present, find the category parent, create it if it doesn't exist. + category_parent = None + if force_category: + category_parent = self.findDefinitions(key=force_category) + category_parent = category_parent[0] if len(category_parent) > 0 else None + + for key, value in settings_dict.items(): + if key in self._definition_cache: + continue + definition = SettingDefinition(key, self, category_parent, self._i18n_catalog) self._definition_cache[key] = definition definition.deserialize(value) - self._definitions.append(definition) + if category_parent: + # Forced category; these are then children of that category, instead of full categories on their own. + category_parent.children.append(definition) + else: + self._definitions.append(definition) for definition in self._definitions: self._updateRelations(definition) - return serialized + def appendAdditionalSettingDefinitions(self, additional_settings: Dict[str, Dict[str, Any]]) -> None: + try: + merge_with_existing_categories = {} + create_new_categories = {} + + for category, values in additional_settings.items(): + if len(self.findDefinitions(key=category)) > 0: + merge_with_existing_categories[category] = values + else: + create_new_categories[category] = values + + if len(create_new_categories) > 0: + self._deserializeDefinitions(create_new_categories) + for category, values in merge_with_existing_categories.items(): + if "children" in values: + for key, value in values["children"].items(): + self._deserializeDefinitions({key: value}, category) + else: + self._deserializeDefinitions(values, category) + + except Exception as ex: + Logger.error(f"Failed to append additional settings from external source because: {str(ex)}") @classmethod def deserializeMetadata(cls, serialized: str, container_id: str) -> List[Dict[str, Any]]: @@ -429,6 +467,7 @@ def _resolveInheritance(self, file_name: str) -> Dict[str, Any]: json_dict = self._loadFile(file_name) if "inherits" in json_dict: + # NOTE: Since load-file isn't cached, this will load base definitions multiple times! inherited = self._resolveInheritance(json_dict["inherits"]) json_dict = self._mergeDicts(inherited, json_dict) From a45418c7ed422fda916be485c50ed5f34ae23bb3 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 26 Jul 2023 08:33:28 +0200 Subject: [PATCH 29/45] Append (plugin) id to additional setting definitions. part of CURA-10722 --- UM/Settings/AdditionalSettingDefinitionAppender.py | 10 ++++++++++ UM/Settings/ContainerRegistry.py | 5 +++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/UM/Settings/AdditionalSettingDefinitionAppender.py b/UM/Settings/AdditionalSettingDefinitionAppender.py index 29e5971166..1be1812463 100644 --- a/UM/Settings/AdditionalSettingDefinitionAppender.py +++ b/UM/Settings/AdditionalSettingDefinitionAppender.py @@ -5,6 +5,16 @@ from UM.PluginObject import PluginObject +def prependIdToSettings(id: str, settings: Dict[str, Dict[str, Any]]) -> Dict[str, Dict[str, Any]]: + result = {} + for key, value in settings.items(): + if isinstance(value, dict): + key = f"{id}:{key}" if ("type" in value and "category" not in value) else key + value = prependIdToSettings(id, value) + result[key] = value + return result + + class AdditionalSettingDefinitionsAppender(PluginObject): def __init__(self) -> None: diff --git a/UM/Settings/ContainerRegistry.py b/UM/Settings/ContainerRegistry.py index b477e321ea..a1a5449118 100644 --- a/UM/Settings/ContainerRegistry.py +++ b/UM/Settings/ContainerRegistry.py @@ -17,7 +17,7 @@ from UM.Settings.EmptyInstanceContainer import EmptyInstanceContainer from UM.Settings.ContainerFormatError import ContainerFormatError from UM.Settings.ContainerProvider import ContainerProvider -from UM.Settings.AdditionalSettingDefinitionAppender import AdditionalSettingDefinitionsAppender +from UM.Settings.AdditionalSettingDefinitionAppender import AdditionalSettingDefinitionsAppender, prependIdToSettings from UM.Settings.constant_instance_containers import empty_container from . import ContainerQuery from UM.Settings.ContainerStack import ContainerStack @@ -122,7 +122,8 @@ def addProvider(self, provider: ContainerProvider) -> None: def addAdditionalSettingDefinitionsAppender(self, appender: AdditionalSettingDefinitionsAppender) -> None: """Adds a provider for additional setting definitions to append to each definition-container.""" - self._additional_setting_definitions_list.append(appender.getAdditionalSettingDefinitions()) + plugin_id_settings = prependIdToSettings(appender.getId(), appender.getAdditionalSettingDefinitions()) + self._additional_setting_definitions_list.append(plugin_id_settings) def findDefinitionContainers(self, **kwargs: Any) -> List[DefinitionContainerInterface]: """Find all DefinitionContainer objects matching certain criteria. From bf710f5a6286f66d8a73541f2ff52f7430cc77aa Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 26 Jul 2023 12:41:41 +0200 Subject: [PATCH 30/45] Be able to use additional settings in formula's. Needed to use underscores instead of :: so that it would work as python variable names. part of CURA-10722 --- .../AdditionalSettingDefinitionAppender.py | 66 ++++++++++++++++--- UM/Settings/ContainerRegistry.py | 3 +- UM/Settings/DefinitionContainer.py | 2 - 3 files changed, 60 insertions(+), 11 deletions(-) diff --git a/UM/Settings/AdditionalSettingDefinitionAppender.py b/UM/Settings/AdditionalSettingDefinitionAppender.py index 1be1812463..1782746113 100644 --- a/UM/Settings/AdditionalSettingDefinitionAppender.py +++ b/UM/Settings/AdditionalSettingDefinitionAppender.py @@ -3,16 +3,66 @@ from typing import Any, Dict from UM.PluginObject import PluginObject +from UM.Settings.SettingDefinition import DefinitionPropertyType, SettingDefinition -def prependIdToSettings(id: str, settings: Dict[str, Dict[str, Any]]) -> Dict[str, Dict[str, Any]]: - result = {} - for key, value in settings.items(): - if isinstance(value, dict): - key = f"{id}:{key}" if ("type" in value and "category" not in value) else key - value = prependIdToSettings(id, value) - result[key] = value - return result +def prependIdToSettings(tag_type: str, tag_id: str, tag_version: str, settings: Dict[str, Any]) -> Dict[str, Any]: + """ This takes the whole (extra) settings-map as defined by the provider, and returns a tag-renamed version. + + Additional (appended) settings will need to be prepended with (an) extra identifier(s)/namespaces to not collide. + This is done for when there are multiple additional settings appenders that might not know about each other. + This includes any formulas, which will also be included in the renaming process. + + Appended settings may not be the same as 'baseline' (so any 'non-appended' settings) settings. + (But may of course clash between different providers and versions, that's the whole point of this function...) + Furthermore, it's assumed that formulas within the appended settings will only use settings either; + - as defined within the baseline, or; + - any other settings defined _by the provider itself_. + + For each key that is renamed, this results in a mapping -> _______ + where '' is the version of the provider, but converted from using points to using underscores. + Example: 'tapdance_factor' might become '_PLUGIN__DancingPrinter__1_2_99__tapdance_factor' + + :param tag_type: Type of the additional settings appender, for example; "PLUGIN". + :param tag_id: ID of the provider. Should be unique. + :param tag_version: Version of the provider. Points will be replaced by underscores. + :param settings: The settings as originally provided. + + :returns: Remapped settings, where each settings-name is properly tagged/'namespaced'. + """ + tag_version = tag_version.replace(".", "_") + + # First get the mapping, so that both the 'headings' and formula's can be renamed at the same time later. + def _getMapping(values: Dict[str, Any]) -> Dict[str, str]: + result = {} + for key, value in values.items(): + mapped_key = key + if isinstance(value, dict): + if "type" in value and value["type"] != "category": + mapped_key = f"_{tag_type}__{tag_id}__{tag_version}__{key}" + result.update(_getMapping(value)) + result[key] = mapped_key + return result + key_map = _getMapping(settings) + + # Get all values that can be functions, so it's known where to replace. + function_type_names = set(SettingDefinition.getPropertyNames(DefinitionPropertyType.Function)) + + # Replace all, both as key-names and their use in formulas. + def _doReplace(values: Dict[str, Any]) -> Dict[str, str]: + result = {} + for key, value in values.items(): + if key in function_type_names and isinstance(value, str): + # Replace key-names in the specified settings-function. + for original, mapped in key_map.items(): + value = value.replace(original, mapped) + elif isinstance(value, dict): + # Replace key-name 'heading'. + key = key_map.get(key, key) + value = _doReplace(value) + result[key] = value + return result + return _doReplace(settings) class AdditionalSettingDefinitionsAppender(PluginObject): diff --git a/UM/Settings/ContainerRegistry.py b/UM/Settings/ContainerRegistry.py index a1a5449118..9514a8f617 100644 --- a/UM/Settings/ContainerRegistry.py +++ b/UM/Settings/ContainerRegistry.py @@ -122,7 +122,8 @@ def addProvider(self, provider: ContainerProvider) -> None: def addAdditionalSettingDefinitionsAppender(self, appender: AdditionalSettingDefinitionsAppender) -> None: """Adds a provider for additional setting definitions to append to each definition-container.""" - plugin_id_settings = prependIdToSettings(appender.getId(), appender.getAdditionalSettingDefinitions()) + additional_settings = appender.getAdditionalSettingDefinitions() + plugin_id_settings = prependIdToSettings("PLUGIN", appender.getId(), appender.getVersion(), additional_settings) self._additional_setting_definitions_list.append(plugin_id_settings) def findDefinitionContainers(self, **kwargs: Any) -> List[DefinitionContainerInterface]: diff --git a/UM/Settings/DefinitionContainer.py b/UM/Settings/DefinitionContainer.py index c41039b06a..140d6a4c5e 100644 --- a/UM/Settings/DefinitionContainer.py +++ b/UM/Settings/DefinitionContainer.py @@ -344,8 +344,6 @@ def _deserializeDefinitions(self, settings_dict: Dict[str, Any], force_category: category_parent = category_parent[0] if len(category_parent) > 0 else None for key, value in settings_dict.items(): - if key in self._definition_cache: - continue definition = SettingDefinition(key, self, category_parent, self._i18n_catalog) self._definition_cache[key] = definition definition.deserialize(value) From 211279aa4c5363f6ccb0c0ec21ccb0f05fcc2c2a Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 26 Jul 2023 14:08:23 +0200 Subject: [PATCH 31/45] Additional settings: Save/load functionality requires all lower-case. When saving to cfg (which is in the zipped 3mf file), the variables are forced to lower case. If a plugin-ID contains capital letters, it would not be able to match the settings it saved and the setting loaded otherwise. part of CURA-10722 --- UM/Settings/AdditionalSettingDefinitionAppender.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/UM/Settings/AdditionalSettingDefinitionAppender.py b/UM/Settings/AdditionalSettingDefinitionAppender.py index 1782746113..e575a5b522 100644 --- a/UM/Settings/AdditionalSettingDefinitionAppender.py +++ b/UM/Settings/AdditionalSettingDefinitionAppender.py @@ -21,7 +21,8 @@ def prependIdToSettings(tag_type: str, tag_id: str, tag_version: str, settings: For each key that is renamed, this results in a mapping -> _______ where '' is the version of the provider, but converted from using points to using underscores. - Example: 'tapdance_factor' might become '_PLUGIN__DancingPrinter__1_2_99__tapdance_factor' + Example: 'tapdance_factor' might become '_plugin__dancingprinter__1_2_99__tapdance_factor' + Also note that all the tag_... parameters will be forced to lower-case. :param tag_type: Type of the additional settings appender, for example; "PLUGIN". :param tag_id: ID of the provider. Should be unique. @@ -30,7 +31,9 @@ def prependIdToSettings(tag_type: str, tag_id: str, tag_version: str, settings: :returns: Remapped settings, where each settings-name is properly tagged/'namespaced'. """ - tag_version = tag_version.replace(".", "_") + tag_type = tag_type.lower() + tag_id = tag_id.lower() + tag_version = tag_version.lower().replace(".", "_") # First get the mapping, so that both the 'headings' and formula's can be renamed at the same time later. def _getMapping(values: Dict[str, Any]) -> Dict[str, str]: From 987fedca546f694a87d10334415f6e02365a5685 Mon Sep 17 00:00:00 2001 From: Remco Burema <41987080+rburema@users.noreply.github.com> Date: Fri, 28 Jul 2023 13:46:58 +0200 Subject: [PATCH 32/45] Apply suggestions from code review. done as part of CURA-10722 Co-authored-by: Jelle Spijker --- UM/Settings/DefinitionContainer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UM/Settings/DefinitionContainer.py b/UM/Settings/DefinitionContainer.py index 140d6a4c5e..6b01470799 100644 --- a/UM/Settings/DefinitionContainer.py +++ b/UM/Settings/DefinitionContainer.py @@ -340,7 +340,7 @@ def _deserializeDefinitions(self, settings_dict: Dict[str, Any], force_category: # When there is a forced category (= parent) present, find the category parent, create it if it doesn't exist. category_parent = None if force_category: - category_parent = self.findDefinitions(key=force_category) + category_parent = self.findDefinitions(key = force_category) category_parent = category_parent[0] if len(category_parent) > 0 else None for key, value in settings_dict.items(): @@ -362,7 +362,7 @@ def appendAdditionalSettingDefinitions(self, additional_settings: Dict[str, Dict create_new_categories = {} for category, values in additional_settings.items(): - if len(self.findDefinitions(key=category)) > 0: + if len(self.findDefinitions(key = category)) > 0: merge_with_existing_categories[category] = values else: create_new_categories[category] = values From 4233101cf7c450843ccba3fe79a8747b3533f4d4 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 28 Jul 2023 14:45:34 +0200 Subject: [PATCH 33/45] Read additional definitions from file, not hardcoded data. You could still override the getAdditionalSettingDefinitions method like before to get the same result as previous, but you can now also just put a path in the definition_file_paths variable to load from there instead. part of CURA-10722 --- .../AdditionalSettingDefinitionAppender.py | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/UM/Settings/AdditionalSettingDefinitionAppender.py b/UM/Settings/AdditionalSettingDefinitionAppender.py index e575a5b522..de88ad4ca3 100644 --- a/UM/Settings/AdditionalSettingDefinitionAppender.py +++ b/UM/Settings/AdditionalSettingDefinitionAppender.py @@ -1,7 +1,12 @@ # Copyright (c) 2023 UltiMaker. # Uranium is released under the terms of the LGPLv3 or higher. -from typing import Any, Dict +import json +from pathlib import Path +import os.path +from typing import Any, Dict, List + +from UM.Logger import Logger from UM.PluginObject import PluginObject from UM.Settings.SettingDefinition import DefinitionPropertyType, SettingDefinition @@ -72,11 +77,28 @@ class AdditionalSettingDefinitionsAppender(PluginObject): def __init__(self) -> None: super().__init__() + self.definition_file_paths: List[Path] = [] def getAdditionalSettingDefinitions(self) -> Dict[str, Dict[str, Any]]: """ - Should return the settings added by this plugin in json format. + Return the settings added by this plugin in json format. + Put values in self.definition_file_paths if you wish to load from files, or override this function otherwise. + The settings should be divided by category (either existing or new ones). Settings in existing categories will be appended, new categories will be created. """ - raise NotImplementedError("A setting_definitions_appender needs to implement getAdditionalSettingDefinitions.") + result = {} + for path in self.definition_file_paths: + if not os.path.exists(path): + Logger.error(f"File {path} with additional settings for '{self.getId()}' doesn't exist.") + continue + try: + with open(path, "r", encoding = "utf-8") as definitions_file: + result.update(json.load(definitions_file)) + except OSError as oex: + Logger.error(f"Could not read additional settings file for '{self.getId()}' because: {str(oex)}") + continue + except json.JSONDecodeError as jex: + Logger.error(f"Could not parse additional settings provided by '{self.getId()}' because: {str(jex)}") + continue + return result From e86b3808683a3e84b43cb50df9ef501b4764f011 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 28 Jul 2023 15:10:34 +0200 Subject: [PATCH 34/45] Get proper type-string for (additional setting definitions) appenders. Not all of them have to be for engine-plugins after all. part of CURA-10722 --- UM/Settings/AdditionalSettingDefinitionAppender.py | 4 ++++ UM/Settings/ContainerRegistry.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/UM/Settings/AdditionalSettingDefinitionAppender.py b/UM/Settings/AdditionalSettingDefinitionAppender.py index de88ad4ca3..0bd6a3117a 100644 --- a/UM/Settings/AdditionalSettingDefinitionAppender.py +++ b/UM/Settings/AdditionalSettingDefinitionAppender.py @@ -77,8 +77,12 @@ class AdditionalSettingDefinitionsAppender(PluginObject): def __init__(self) -> None: super().__init__() + self.appender_type = "EXTRA" self.definition_file_paths: List[Path] = [] + def getAppenderType(self) -> str: + return self.appender_type + def getAdditionalSettingDefinitions(self) -> Dict[str, Dict[str, Any]]: """ Return the settings added by this plugin in json format. diff --git a/UM/Settings/ContainerRegistry.py b/UM/Settings/ContainerRegistry.py index 9514a8f617..36b08bde2c 100644 --- a/UM/Settings/ContainerRegistry.py +++ b/UM/Settings/ContainerRegistry.py @@ -123,7 +123,7 @@ def addAdditionalSettingDefinitionsAppender(self, appender: AdditionalSettingDef """Adds a provider for additional setting definitions to append to each definition-container.""" additional_settings = appender.getAdditionalSettingDefinitions() - plugin_id_settings = prependIdToSettings("PLUGIN", appender.getId(), appender.getVersion(), additional_settings) + plugin_id_settings = prependIdToSettings(appender.getAppenderType(), appender.getId(), appender.getVersion(), additional_settings) self._additional_setting_definitions_list.append(plugin_id_settings) def findDefinitionContainers(self, **kwargs: Any) -> List[DefinitionContainerInterface]: From fff404e1a97e7fb46eeb25bda50f7889105495e4 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 1 Aug 2023 09:14:23 +0200 Subject: [PATCH 35/45] Additional settings: Document class and move function. part of CURA-10722 --- .../AdditionalSettingDefinitionAppender.py | 138 ++++++++++-------- UM/Settings/ContainerRegistry.py | 4 +- UM/Settings/DefinitionContainer.py | 5 + 3 files changed, 87 insertions(+), 60 deletions(-) diff --git a/UM/Settings/AdditionalSettingDefinitionAppender.py b/UM/Settings/AdditionalSettingDefinitionAppender.py index 0bd6a3117a..b21f1db2de 100644 --- a/UM/Settings/AdditionalSettingDefinitionAppender.py +++ b/UM/Settings/AdditionalSettingDefinitionAppender.py @@ -11,69 +11,22 @@ from UM.Settings.SettingDefinition import DefinitionPropertyType, SettingDefinition -def prependIdToSettings(tag_type: str, tag_id: str, tag_version: str, settings: Dict[str, Any]) -> Dict[str, Any]: - """ This takes the whole (extra) settings-map as defined by the provider, and returns a tag-renamed version. - - Additional (appended) settings will need to be prepended with (an) extra identifier(s)/namespaces to not collide. - This is done for when there are multiple additional settings appenders that might not know about each other. - This includes any formulas, which will also be included in the renaming process. - - Appended settings may not be the same as 'baseline' (so any 'non-appended' settings) settings. - (But may of course clash between different providers and versions, that's the whole point of this function...) - Furthermore, it's assumed that formulas within the appended settings will only use settings either; - - as defined within the baseline, or; - - any other settings defined _by the provider itself_. - - For each key that is renamed, this results in a mapping -> _______ - where '' is the version of the provider, but converted from using points to using underscores. - Example: 'tapdance_factor' might become '_plugin__dancingprinter__1_2_99__tapdance_factor' - Also note that all the tag_... parameters will be forced to lower-case. - - :param tag_type: Type of the additional settings appender, for example; "PLUGIN". - :param tag_id: ID of the provider. Should be unique. - :param tag_version: Version of the provider. Points will be replaced by underscores. - :param settings: The settings as originally provided. - - :returns: Remapped settings, where each settings-name is properly tagged/'namespaced'. +class AdditionalSettingDefinitionsAppender(PluginObject): """ - tag_type = tag_type.lower() - tag_id = tag_id.lower() - tag_version = tag_version.lower().replace(".", "_") + This class is a way for plugins to append additional settings, not defined by/for the main program itself. - # First get the mapping, so that both the 'headings' and formula's can be renamed at the same time later. - def _getMapping(values: Dict[str, Any]) -> Dict[str, str]: - result = {} - for key, value in values.items(): - mapped_key = key - if isinstance(value, dict): - if "type" in value and value["type"] != "category": - mapped_key = f"_{tag_type}__{tag_id}__{tag_version}__{key}" - result.update(_getMapping(value)) - result[key] = mapped_key - return result - key_map = _getMapping(settings) + Each plugin needs to register as either a 'setting_definitions_appender' or 'backend_plugin'. - # Get all values that can be functions, so it's known where to replace. - function_type_names = set(SettingDefinition.getPropertyNames(DefinitionPropertyType.Function)) + Any implementation also needs to fill 'self.definition_file_paths' with a list of files with setting definitions. + Each file should be a json list of setting categories, either matching existing names, or be a new category. + Each category and setting has the same json structure as the main settings otherwise. - # Replace all, both as key-names and their use in formulas. - def _doReplace(values: Dict[str, Any]) -> Dict[str, str]: - result = {} - for key, value in values.items(): - if key in function_type_names and isinstance(value, str): - # Replace key-names in the specified settings-function. - for original, mapped in key_map.items(): - value = value.replace(original, mapped) - elif isinstance(value, dict): - # Replace key-name 'heading'. - key = key_map.get(key, key) - value = _doReplace(value) - result[key] = value - return result - return _doReplace(settings) + It's also possible to set the 'self.appender_type', if there are many kinds of plugins to implement this, + in order to prevent name-clashes. - -class AdditionalSettingDefinitionsAppender(PluginObject): + Lastly, if setting-definitions are to be made on the fly by the plugin, override 'getAdditionalSettingDefinitions', + instead of providing the files. This should then return a dict, as if parsed by json. + """ def __init__(self) -> None: super().__init__() @@ -81,6 +34,9 @@ def __init__(self) -> None: self.definition_file_paths: List[Path] = [] def getAppenderType(self) -> str: + """ + Return an extra identifier prepended to the setting internal id, to prevent name-clashes with other plugins. + """ return self.appender_type def getAdditionalSettingDefinitions(self) -> Dict[str, Dict[str, Any]]: @@ -90,6 +46,8 @@ def getAdditionalSettingDefinitions(self) -> Dict[str, Dict[str, Any]]: The settings should be divided by category (either existing or new ones). Settings in existing categories will be appended, new categories will be created. + + :return: """ result = {} for path in self.definition_file_paths: @@ -106,3 +64,67 @@ def getAdditionalSettingDefinitions(self) -> Dict[str, Dict[str, Any]]: Logger.error(f"Could not parse additional settings provided by '{self.getId()}' because: {str(jex)}") continue return result + + @classmethod + def prependIdToSettings(cls, tag_type: str, tag_id: str, tag_version: str, settings: Dict[str, Any]) -> Dict[str, Any]: + """ This takes the whole (extra) settings-map as defined by the provider, and returns a tag-renamed version. + + Additional (appended) settings will need to be prepended with (an) extra identifier(s)/namespaces to not collide. + This is done for when there are multiple additional settings appenders that might not know about each other. + This includes any formulas, which will also be included in the renaming process. + + Appended settings may not be the same as 'baseline' (so any 'non-appended' settings) settings. + (But may of course clash between different providers and versions, that's the whole point of this function...) + Furthermore, it's assumed that formulas within the appended settings will only use settings either; + - as defined within the baseline, or; + - any other settings defined _by the provider itself_. + + For each key that is renamed, this results in a mapping -> _______ + where '' is the version of the provider, but converted from using points to using underscores. + Example: 'tapdance_factor' might become '_plugin__dancingprinter__1_2_99__tapdance_factor' + Also note that all the tag_... parameters will be forced to lower-case. + + :param tag_type: Type of the additional settings appender, for example; "PLUGIN". + :param tag_id: ID of the provider. Should be unique. + :param tag_version: Version of the provider. Points will be replaced by underscores. + :param settings: The settings as originally provided. + + :returns: Remapped settings, where each settings-name is properly tagged/'namespaced'. + """ + tag_type = tag_type.lower() + tag_id = tag_id.lower() + tag_version = tag_version.lower().replace(".", "_") + + # First get the mapping, so that both the 'headings' and formula's can be renamed at the same time later. + def _getMapping(values: Dict[str, Any]) -> Dict[str, str]: + result = {} + for key, value in values.items(): + mapped_key = key + if isinstance(value, dict): + if "type" in value and value["type"] != "category": + mapped_key = f"_{tag_type}__{tag_id}__{tag_version}__{key}" + result.update(_getMapping(value)) + result[key] = mapped_key + return result + + key_map = _getMapping(settings) + + # Get all values that can be functions, so it's known where to replace. + function_type_names = set(SettingDefinition.getPropertyNames(DefinitionPropertyType.Function)) + + # Replace all, both as key-names and their use in formulas. + def _doReplace(values: Dict[str, Any]) -> Dict[str, str]: + result = {} + for key, value in values.items(): + if key in function_type_names and isinstance(value, str): + # Replace key-names in the specified settings-function. + for original, mapped in key_map.items(): + value = value.replace(original, mapped) + elif isinstance(value, dict): + # Replace key-name 'heading'. + key = key_map.get(key, key) + value = _doReplace(value) + result[key] = value + return result + + return _doReplace(settings) diff --git a/UM/Settings/ContainerRegistry.py b/UM/Settings/ContainerRegistry.py index 36b08bde2c..4752df06c6 100644 --- a/UM/Settings/ContainerRegistry.py +++ b/UM/Settings/ContainerRegistry.py @@ -17,7 +17,7 @@ from UM.Settings.EmptyInstanceContainer import EmptyInstanceContainer from UM.Settings.ContainerFormatError import ContainerFormatError from UM.Settings.ContainerProvider import ContainerProvider -from UM.Settings.AdditionalSettingDefinitionAppender import AdditionalSettingDefinitionsAppender, prependIdToSettings +from UM.Settings.AdditionalSettingDefinitionAppender import AdditionalSettingDefinitionsAppender from UM.Settings.constant_instance_containers import empty_container from . import ContainerQuery from UM.Settings.ContainerStack import ContainerStack @@ -123,7 +123,7 @@ def addAdditionalSettingDefinitionsAppender(self, appender: AdditionalSettingDef """Adds a provider for additional setting definitions to append to each definition-container.""" additional_settings = appender.getAdditionalSettingDefinitions() - plugin_id_settings = prependIdToSettings(appender.getAppenderType(), appender.getId(), appender.getVersion(), additional_settings) + plugin_id_settings = AdditionalSettingDefinitionsAppender.prependIdToSettings(appender.getAppenderType(), appender.getId(), appender.getVersion(), additional_settings) self._additional_setting_definitions_list.append(plugin_id_settings) def findDefinitionContainers(self, **kwargs: Any) -> List[DefinitionContainerInterface]: diff --git a/UM/Settings/DefinitionContainer.py b/UM/Settings/DefinitionContainer.py index 6b01470799..275efdb915 100644 --- a/UM/Settings/DefinitionContainer.py +++ b/UM/Settings/DefinitionContainer.py @@ -357,6 +357,11 @@ def _deserializeDefinitions(self, settings_dict: Dict[str, Any], force_category: self._updateRelations(definition) def appendAdditionalSettingDefinitions(self, additional_settings: Dict[str, Dict[str, Any]]) -> None: + """ + Appends setting-definitions not defined for/by the main program (for example, a plugin) to this container. + See also the Settings.AdditionalSettingDefinitionAppender class. + :param additional_settings: A dictionary of category-name to categories, each containing setting-definitions. + """ try: merge_with_existing_categories = {} create_new_categories = {} From cd3c464b2265b4bac6780b361ebf5be3a6062490 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 1 Aug 2023 16:18:30 +0200 Subject: [PATCH 36/45] Additional Setting Definitions: Improve encapsulation. The prepend method was only ever used by the Appender anyway. This is simpler. part of CURA-10722 --- .../AdditionalSettingDefinitionAppender.py | 17 ++++++++++------- UM/Settings/ContainerRegistry.py | 4 +--- UM/Settings/DefinitionContainer.py | 3 +++ 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/UM/Settings/AdditionalSettingDefinitionAppender.py b/UM/Settings/AdditionalSettingDefinitionAppender.py index b21f1db2de..1c7157a9cb 100644 --- a/UM/Settings/AdditionalSettingDefinitionAppender.py +++ b/UM/Settings/AdditionalSettingDefinitionAppender.py @@ -47,7 +47,11 @@ def getAdditionalSettingDefinitions(self) -> Dict[str, Dict[str, Any]]: The settings should be divided by category (either existing or new ones). Settings in existing categories will be appended, new categories will be created. - :return: + Setting names (not labels) will be post-processed ('mangled') internally to prevent name-clashes. + NOTE: The 'mangled' names are also the ones send out to any backend! + (See the _prependIdToSettings function below for a more precise explanation.) + + :return: Dictionary of settings-categories, containing settings-definitions (with post-processed names). """ result = {} for path in self.definition_file_paths: @@ -63,10 +67,9 @@ def getAdditionalSettingDefinitions(self) -> Dict[str, Dict[str, Any]]: except json.JSONDecodeError as jex: Logger.error(f"Could not parse additional settings provided by '{self.getId()}' because: {str(jex)}") continue - return result + return self._prependIdToSettings(result) - @classmethod - def prependIdToSettings(cls, tag_type: str, tag_id: str, tag_version: str, settings: Dict[str, Any]) -> Dict[str, Any]: + def _prependIdToSettings(self, settings: Dict[str, Any]) -> Dict[str, Any]: """ This takes the whole (extra) settings-map as defined by the provider, and returns a tag-renamed version. Additional (appended) settings will need to be prepended with (an) extra identifier(s)/namespaces to not collide. @@ -91,9 +94,9 @@ def prependIdToSettings(cls, tag_type: str, tag_id: str, tag_version: str, setti :returns: Remapped settings, where each settings-name is properly tagged/'namespaced'. """ - tag_type = tag_type.lower() - tag_id = tag_id.lower() - tag_version = tag_version.lower().replace(".", "_") + tag_type = self.getAppenderType().lower() + tag_id = self.getId().lower() + tag_version = self.getVersion().lower().replace(".", "_") # First get the mapping, so that both the 'headings' and formula's can be renamed at the same time later. def _getMapping(values: Dict[str, Any]) -> Dict[str, str]: diff --git a/UM/Settings/ContainerRegistry.py b/UM/Settings/ContainerRegistry.py index 4752df06c6..b477e321ea 100644 --- a/UM/Settings/ContainerRegistry.py +++ b/UM/Settings/ContainerRegistry.py @@ -122,9 +122,7 @@ def addProvider(self, provider: ContainerProvider) -> None: def addAdditionalSettingDefinitionsAppender(self, appender: AdditionalSettingDefinitionsAppender) -> None: """Adds a provider for additional setting definitions to append to each definition-container.""" - additional_settings = appender.getAdditionalSettingDefinitions() - plugin_id_settings = AdditionalSettingDefinitionsAppender.prependIdToSettings(appender.getAppenderType(), appender.getId(), appender.getVersion(), additional_settings) - self._additional_setting_definitions_list.append(plugin_id_settings) + self._additional_setting_definitions_list.append(appender.getAdditionalSettingDefinitions()) def findDefinitionContainers(self, **kwargs: Any) -> List[DefinitionContainerInterface]: """Find all DefinitionContainer objects matching certain criteria. diff --git a/UM/Settings/DefinitionContainer.py b/UM/Settings/DefinitionContainer.py index 275efdb915..c1e6440fa6 100644 --- a/UM/Settings/DefinitionContainer.py +++ b/UM/Settings/DefinitionContainer.py @@ -359,7 +359,10 @@ def _deserializeDefinitions(self, settings_dict: Dict[str, Any], force_category: def appendAdditionalSettingDefinitions(self, additional_settings: Dict[str, Dict[str, Any]]) -> None: """ Appends setting-definitions not defined for/by the main program (for example, a plugin) to this container. + + Additional settings are always assumed to come in the form of categories with child-settings. See also the Settings.AdditionalSettingDefinitionAppender class. + :param additional_settings: A dictionary of category-name to categories, each containing setting-definitions. """ try: From fc48ce78639074e5674a855d83e308e172be703b Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 1 Aug 2023 16:19:47 +0200 Subject: [PATCH 37/45] Add unit-tests for AdditionalSettingsAppender. part of CURA-10722 --- .../Settings/TestAppendAdditionalSettings.py | 52 +++++++++++++++++++ .../append_extra_settings.def.json | 44 ++++++++++++++++ tests/TestTrust.py | 3 ++ 3 files changed, 99 insertions(+) create mode 100644 tests/Settings/TestAppendAdditionalSettings.py create mode 100644 tests/Settings/definitions/append_extra_settings.def.json diff --git a/tests/Settings/TestAppendAdditionalSettings.py b/tests/Settings/TestAppendAdditionalSettings.py new file mode 100644 index 0000000000..036adc327c --- /dev/null +++ b/tests/Settings/TestAppendAdditionalSettings.py @@ -0,0 +1,52 @@ +# Copyright (c) 2023 UltiMaker +# Uranium is released under the terms of the LGPLv3 or higher. + +import os +import pytest +from unittest.mock import MagicMock, patch + +from UM.Settings.AdditionalSettingDefinitionAppender import AdditionalSettingDefinitionsAppender +from UM.Settings.DefinitionContainer import DefinitionContainer +from UM.VersionUpgradeManager import VersionUpgradeManager + + +class PluginTestClass(AdditionalSettingDefinitionsAppender): + def __init__(self) -> None: + super().__init__() + self._plugin_id = "RealityPerforator" + self._version = "7.8.9" + self.appender_type = "CLOWNS" + self.definition_file_paths = [os.path.join(os.path.dirname(os.path.abspath(__file__)), "definitions", "append_extra_settings.def.json")] + + +def test_AdditionalSettingNames(): + plugin = PluginTestClass() + settings = plugin.getAdditionalSettingDefinitions() + + assert "test_setting" in settings + assert "category_too" in settings + assert "children" in settings["test_setting"] + assert "children" in settings["category_too"] + + assert "_clowns__realityperforator__7_8_9__glombump" in settings["test_setting"]["children"] + assert "_clowns__realityperforator__7_8_9__zharbler" in settings["category_too"]["children"] + + +def test_AdditionalSettingContainer(upgrade_manager: VersionUpgradeManager): + plugin = PluginTestClass() + settings = plugin.getAdditionalSettingDefinitions() + + definition_container = DefinitionContainer("TheSunIsADeadlyLazer") + with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "definitions", "children.def.json"), encoding = "utf-8") as data: + definition_container.deserialize(data.read()) + definition_container.appendAdditionalSettingDefinitions(settings) + + # 'merged' setting-categories should definitely be in the relevant container (as well as the original ones): + assert len(definition_container.findDefinitions(key="test_setting")) == 1 + kid_keys = [x.key for x in definition_container.findDefinitions(key="test_setting")[0].children] + assert "test_child_0" in kid_keys + assert "test_child_1" in kid_keys + assert "_clowns__realityperforator__7_8_9__glombump" in kid_keys + + # other settings (from new categories) are added 'dry' to the container: + assert "_clowns__realityperforator__7_8_9__zharbler" in definition_container.getAllKeys() diff --git a/tests/Settings/definitions/append_extra_settings.def.json b/tests/Settings/definitions/append_extra_settings.def.json new file mode 100644 index 0000000000..61231193c2 --- /dev/null +++ b/tests/Settings/definitions/append_extra_settings.def.json @@ -0,0 +1,44 @@ +{ + "test_setting": + { + "children": + { + "glombump": + { + "label": "Snosmozea", + "description": "Sudoriferous.", + "unit": "mm/s", + "type": "float", + "minimum_value": "0.1", + "maximum_value_warning": "150", + "maximum_value": "500", + "default_value": 60, + "settable_per_mesh": true + } + } + }, + "spoot": + { + "label": "Warble!", + "type": "category", + "description": "Blomblimg", + "icon": "Printer", + "children": + { + "zharbler": + { + "label": "Frumg", + "description": "Pleonastic Pellerator.", + "unit": "mm/s", + "type": "float", + "minimum_value": "0.1", + "maximum_value_warning": "150", + "maximum_value": "500", + "default_value": 60, + "value": "glombump * 1.2", + "settable_per_mesh": false + } + } + } +} + diff --git a/tests/TestTrust.py b/tests/TestTrust.py index 3074c91980..3ce15dfa61 100644 --- a/tests/TestTrust.py +++ b/tests/TestTrust.py @@ -1,3 +1,6 @@ +# Copyright (c) 2023 UltiMaker +# Uranium is released under the terms of the LGPLv3 or higher. + import copy import json from unittest.mock import MagicMock, patch From e30ba991ea156a26e25c5fb81832dde0c9da5e27 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 1 Aug 2023 16:59:18 +0200 Subject: [PATCH 38/45] Fix unit-test(s). part of CURA-10722 --- tests/Settings/TestAppendAdditionalSettings.py | 2 +- .../append_extra_settings.def.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename tests/Settings/{definitions => additional_settings}/append_extra_settings.def.json (97%) diff --git a/tests/Settings/TestAppendAdditionalSettings.py b/tests/Settings/TestAppendAdditionalSettings.py index 036adc327c..fd3c28f5a5 100644 --- a/tests/Settings/TestAppendAdditionalSettings.py +++ b/tests/Settings/TestAppendAdditionalSettings.py @@ -16,7 +16,7 @@ def __init__(self) -> None: self._plugin_id = "RealityPerforator" self._version = "7.8.9" self.appender_type = "CLOWNS" - self.definition_file_paths = [os.path.join(os.path.dirname(os.path.abspath(__file__)), "definitions", "append_extra_settings.def.json")] + self.definition_file_paths = [os.path.join(os.path.dirname(os.path.abspath(__file__)), "additional_settings", "append_extra_settings.def.json")] def test_AdditionalSettingNames(): diff --git a/tests/Settings/definitions/append_extra_settings.def.json b/tests/Settings/additional_settings/append_extra_settings.def.json similarity index 97% rename from tests/Settings/definitions/append_extra_settings.def.json rename to tests/Settings/additional_settings/append_extra_settings.def.json index 61231193c2..a443fa2084 100644 --- a/tests/Settings/definitions/append_extra_settings.def.json +++ b/tests/Settings/additional_settings/append_extra_settings.def.json @@ -17,7 +17,7 @@ } } }, - "spoot": + "category_too": { "label": "Warble!", "type": "category", From d0c8ca9b896caf79fc4ba49e02a57ff80f7db470 Mon Sep 17 00:00:00 2001 From: Casper Lamboo Date: Thu, 3 Aug 2023 13:04:39 +0200 Subject: [PATCH 39/45] Use "PLUGIN" as default `appender_type` --- UM/Settings/AdditionalSettingDefinitionAppender.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UM/Settings/AdditionalSettingDefinitionAppender.py b/UM/Settings/AdditionalSettingDefinitionAppender.py index 1c7157a9cb..bda52ed838 100644 --- a/UM/Settings/AdditionalSettingDefinitionAppender.py +++ b/UM/Settings/AdditionalSettingDefinitionAppender.py @@ -30,7 +30,7 @@ class AdditionalSettingDefinitionsAppender(PluginObject): def __init__(self) -> None: super().__init__() - self.appender_type = "EXTRA" + self.appender_type = "PLUGIN" self.definition_file_paths: List[Path] = [] def getAppenderType(self) -> str: From 18bac311e0f6df965e251b8a9ab68a4ab6114b85 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 4 Aug 2023 17:35:21 +0200 Subject: [PATCH 40/45] Internal type of an enum/options definition should be an OrderedDict. So force it to be that way if possible. belatedly part of CURA-10722 --- UM/Settings/SettingDefinition.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/UM/Settings/SettingDefinition.py b/UM/Settings/SettingDefinition.py index 2249496107..2ab447b821 100644 --- a/UM/Settings/SettingDefinition.py +++ b/UM/Settings/SettingDefinition.py @@ -672,6 +672,11 @@ def _deserialize_dict(self, serialized: Dict[str, Any]) -> None: if value not in self.__type_definitions: raise ValueError("Type {0} is not a correct setting type".format(value)) + if key == "options" and not isinstance(value, collections.OrderedDict): + if not isinstance(value, dict): + raise ValueError(f"Type {value} is not a correct value for an enum-definition.") + value = collections.OrderedDict(value) + if self.__property_definitions[key]["type"] == DefinitionPropertyType.Any: self.__property_values[key] = value elif self.__property_definitions[key]["type"] == DefinitionPropertyType.String: From d861b141840ff85e219511feb9e2f1add4e3f4d6 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 4 Aug 2023 17:36:46 +0200 Subject: [PATCH 41/45] Scout some string formatting in the neighbourhood. while working on a belated fix added to CURA-10722 --- UM/Settings/SettingDefinition.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/UM/Settings/SettingDefinition.py b/UM/Settings/SettingDefinition.py index 2ab447b821..69458b919e 100644 --- a/UM/Settings/SettingDefinition.py +++ b/UM/Settings/SettingDefinition.py @@ -665,12 +665,12 @@ def _deserialize_dict(self, serialized: Dict[str, Any]) -> None: continue if key not in self.__property_definitions: - Logger.log("w", "Unrecognised property %s in setting %s", key, self._key) + Logger.log("w", f"Unrecognised property {key} in setting {self._key}") continue if key == "type": if value not in self.__type_definitions: - raise ValueError("Type {0} is not a correct setting type".format(value)) + raise ValueError(f"Type {value} is not a correct setting type.") if key == "options" and not isinstance(value, collections.OrderedDict): if not isinstance(value, dict): @@ -686,11 +686,11 @@ def _deserialize_dict(self, serialized: Dict[str, Any]) -> None: elif self.__property_definitions[key]["type"] == DefinitionPropertyType.Function: self.__property_values[key] = SettingFunction.SettingFunction(str(value)) else: - Logger.log("w", "Unknown DefinitionPropertyType (%s) for key %s", key, self.__property_definitions[key]["type"]) + Logger.log("w", f"Unknown DefinitionPropertyType ({key}) for key {self.__property_definitions[key]['type']}") for key in filter(lambda i: self.__property_definitions[i]["required"], self.__property_definitions): if key not in self.__property_values: - raise AttributeError("Setting {0} is missing required property {1}".format(self._key, key)) + raise AttributeError(f"Setting {self._key} is missing required property {key}") self.__ancestors = self._updateAncestors() self.__descendants = self._updateDescendants() From 73bbf21e0869bc2f00a73e6489d28e9324c63ee2 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Tue, 8 Aug 2023 12:17:08 +0200 Subject: [PATCH 42/45] Disable copy paste when either 3mf reader or writer is disabled CURA-7913 --- UM/PackageManager.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/UM/PackageManager.py b/UM/PackageManager.py index ad069d29a3..b7cb78a6c2 100644 --- a/UM/PackageManager.py +++ b/UM/PackageManager.py @@ -453,6 +453,16 @@ def removeFromDismissedPackages(self, package: str) -> None: def isPackageInstalled(self, package_id: str) -> bool: return self.getInstalledPackageInfo(package_id) is not None + # Checks if the given package is installed and enabled. + @pyqtProperty("QList", notify=installedPackagesChanged) + def allEnabledPackages(self) -> List[str]: + enabled_packages = [] + for package_id in self.getAllInstalledPackageIDs(): + package_info = self.getInstalledPackageInfo(package_id) + if package_info is not None and package_info["is_active"]: + enabled_packages.append(package_id) + return enabled_packages + @pyqtSlot(QUrl) def installPackageViaDragAndDrop(self, file_url: str) -> None: """This is called by drag-and-dropping curapackage files.""" From 4e0669700294b2cb2cf83be77911dfe13da999e6 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Tue, 29 Aug 2023 08:24:00 +0200 Subject: [PATCH 43/45] Use pyarcus 10951 Contributes to CURA-10951 and CURA-10446 --- conanfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conanfile.py b/conanfile.py index e0aa286bac..a32fd69495 100644 --- a/conanfile.py +++ b/conanfile.py @@ -95,7 +95,7 @@ def validate(self): raise ConanInvalidConfiguration("Only versions 5+ are support") def requirements(self): - self.requires("pyarcus/(latest)@ultimaker/cura_10475") + self.requires("pyarcus/(latest)@ultimaker/cura_10951") self.requires("cpython/3.10.4") def build_requirements(self): From 74d502d853518deef989c502af5b3b3a93001377 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 8 Sep 2023 22:44:52 +0200 Subject: [PATCH 44/45] Require main-branch package of pyArcus. since CURA-10951 has been merged. --- conanfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conanfile.py b/conanfile.py index eeffb2f000..2145b99b0d 100644 --- a/conanfile.py +++ b/conanfile.py @@ -95,7 +95,7 @@ def validate(self): raise ConanInvalidConfiguration("Only versions 5+ are support") def requirements(self): - self.requires("pyarcus/(latest)@ultimaker/cura_10951") + self.requires("pyarcus/(latest)@ultimaker/testing") self.requires("cpython/3.10.4") def build_requirements(self): From bf0f6b0a16397fe4a6721052e448266cf15d9182 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Mon, 11 Sep 2023 08:23:43 +0200 Subject: [PATCH 45/45] Pin pyArcus, pySavitar and pynestd2d to released 5.3.0 Contributes CURA-10951 and CURA-10475 --- conanfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conanfile.py b/conanfile.py index 2145b99b0d..896142cf4c 100644 --- a/conanfile.py +++ b/conanfile.py @@ -95,7 +95,7 @@ def validate(self): raise ConanInvalidConfiguration("Only versions 5+ are support") def requirements(self): - self.requires("pyarcus/(latest)@ultimaker/testing") + self.requires("pyarcus/5.3.0") self.requires("cpython/3.10.4") def build_requirements(self):