From 382adf57dfad00fe69ded8a2f99af5d7b99d5450 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 10 Oct 2018 07:55:49 +0200 Subject: [PATCH 01/31] Use 0.1 layer height as normal for Ender 3 --- resources/definitions/creality_ender3.def.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/definitions/creality_ender3.def.json b/resources/definitions/creality_ender3.def.json index 9745f28a93..d3765ca9b3 100755 --- a/resources/definitions/creality_ender3.def.json +++ b/resources/definitions/creality_ender3.def.json @@ -60,7 +60,7 @@ "default_value": 20 }, "layer_height": { - "default_value": 0.15 + "default_value": 0.10 }, "layer_height_0": { "default_value": 0.2 From 10b5584ca68457dd25659a3273d9375602667c19 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 10 Oct 2018 16:24:13 +0200 Subject: [PATCH 02/31] [CURA-5483] Support more than just the UM3(E) for the firmware-update-check (add S5 only for now). --- .../FirmwareUpdateChecker.py | 22 +++- .../FirmwareUpdateCheckerJob.py | 103 ++++++++++++++---- 2 files changed, 100 insertions(+), 25 deletions(-) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py index f01e8cb276..80a954c1cc 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py @@ -12,23 +12,34 @@ from UM.Settings.ContainerRegistry import ContainerRegistry from cura.Settings.GlobalStack import GlobalStack -from .FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob +from .FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob, MachineId, get_settings_key_for_machine i18n_catalog = i18nCatalog("cura") - ## This Extension checks for new versions of the firmware based on the latest checked version number. # The plugin is currently only usable for applications maintained by Ultimaker. But it should be relatively easy # to change it to work for other applications. class FirmwareUpdateChecker(Extension): JEDI_VERSION_URL = "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources" + UM_NEW_URL_TEMPLATE = "http://software.ultimaker.com/releases/firmware/{0}/stable/version.txt" + VERSION_URLS_PER_MACHINE = \ + { + MachineId.UM3: [JEDI_VERSION_URL, UM_NEW_URL_TEMPLATE.format(MachineId.UM3.value)], + MachineId.UM3E: [JEDI_VERSION_URL, UM_NEW_URL_TEMPLATE.format(MachineId.UM3E.value)], + MachineId.S5: [UM_NEW_URL_TEMPLATE.format(MachineId.S5.value)] + } + # The 'new'-style URL is the only way to check for S5 firmware, + # and in the future, the UM3 line will also switch over, but for now the old 'JEDI'-style URL is still needed. + # TODO: Parse all of that from a file, because this will be a big mess of large static values which gets worse with each printer. + # See also the to-do in FirmWareCheckerJob. def __init__(self): super().__init__() # Initialize the Preference called `latest_checked_firmware` that stores the last version - # checked for the UM3. In the future if we need to check other printers' firmware - Application.getInstance().getPreferences().addPreference("info/latest_checked_firmware", "") + # checked for each printer. + for machine_id in MachineId: + Application.getInstance().getPreferences().addPreference(get_settings_key_for_machine(machine_id), "") # Listen to a Signal that indicates a change in the list of printers, just if the user has enabled the # 'check for updates' option @@ -68,7 +79,8 @@ class FirmwareUpdateChecker(Extension): Logger.log("i", "A firmware update check is already running, do nothing.") return - self._check_job = FirmwareUpdateCheckerJob(container = container, silent = silent, url = self.JEDI_VERSION_URL, + self._check_job = FirmwareUpdateCheckerJob(container = container, silent = silent, + urls = self.VERSION_URLS_PER_MACHINE, callback = self._onActionTriggered, set_download_url_callback = self._onSetDownloadUrl) self._check_job.start() diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index eadacf2c02..658e820b4b 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -1,10 +1,13 @@ # Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +from enum import Enum, unique + from UM.Application import Application from UM.Message import Message from UM.Logger import Logger from UM.Job import Job +from UM.Version import Version import urllib.request import codecs @@ -12,49 +15,104 @@ import codecs from UM.i18n import i18nCatalog i18n_catalog = i18nCatalog("cura") +# For UM-machines, these need to match the unique firmware-ID (also used in the URLs), i.o.t. only define in one place. +@unique +class MachineId(Enum): + UM3 = 9066 + UM3E = 9511 + S5 = 9051 + + +def get_settings_key_for_machine(machine_id: MachineId) -> str: + return "info/latest_checked_firmware_for_{0}".format(machine_id.value) + + +def default_parse_version_response(response: str) -> Version: + raw_str = response.split('\n', 1)[0].rstrip() + return Version(raw_str.split('.')) # Split it into a list; the default parsing of 'single string' is different. + ## This job checks if there is an update available on the provided URL. class FirmwareUpdateCheckerJob(Job): - def __init__(self, container = None, silent = False, url = None, callback = None, set_download_url_callback = None): + MACHINE_PER_NAME = \ + { + "ultimaker 3": MachineId.UM3, + "ultimaker 3 extended": MachineId.UM3E, + "ultimaker s5": MachineId.S5 + } + PARSE_VERSION_URL_PER_MACHINE = \ + { + MachineId.UM3: default_parse_version_response, + MachineId.UM3E: default_parse_version_response, + MachineId.S5: default_parse_version_response + } + REDIRECT_USER_PER_MACHINE = \ + { + MachineId.UM3: "https://ultimaker.com/en/resources/20500-upgrade-firmware", + MachineId.UM3E: "https://ultimaker.com/en/resources/20500-upgrade-firmware", + MachineId.S5: "https://ultimaker.com/en/resources/20500-upgrade-firmware" + } + # TODO: Parse all of that from a file, because this will be a big mess of large static values which gets worse with each printer. + + def __init__(self, container=None, silent=False, urls=None, callback=None, set_download_url_callback=None): super().__init__() self._container = container self.silent = silent - self._url = url + self._urls = urls self._callback = callback self._set_download_url_callback = set_download_url_callback + application_name = Application.getInstance().getApplicationName() + application_version = Application.getInstance().getVersion() + self._headers = {"User-Agent": "%s - %s" % (application_name, application_version)} + + def getUrlResponse(self, url: str) -> str: + request = urllib.request.Request(url, headers=self._headers) + current_version_file = urllib.request.urlopen(request) + reader = codecs.getreader("utf-8") + + return reader(current_version_file).read(firstline=True) + + def getCurrentVersionForMachine(self, machine_id: MachineId) -> Version: + max_version = Version([0, 0, 0]) + + machine_urls = self._urls.get(machine_id) + parse_function = self.PARSE_VERSION_URL_PER_MACHINE.get(machine_id) + if machine_urls is not None and parse_function is not None: + for url in machine_urls: + version = parse_function(self.getUrlResponse(url)) + if version > max_version: + max_version = version + + if max_version < Version([0, 0, 1]): + Logger.log('w', "MachineID {0} not handled!".format(repr(machine_id))) + + return max_version + def run(self): - if not self._url: + if not self._urls or self._urls is None: Logger.log("e", "Can not check for a new release. URL not set!") return try: - application_name = Application.getInstance().getApplicationName() - headers = {"User-Agent": "%s - %s" % (application_name, Application.getInstance().getVersion())} - request = urllib.request.Request(self._url, headers = headers) - current_version_file = urllib.request.urlopen(request) - reader = codecs.getreader("utf-8") - # get machine name from the definition container machine_name = self._container.definition.getName() machine_name_parts = machine_name.lower().split(" ") # If it is not None, then we compare between the checked_version and the current_version - # Now we just do that if the active printer is Ultimaker 3 or Ultimaker 3 Extended or any - # other Ultimaker 3 that will come in the future - if len(machine_name_parts) >= 2 and machine_name_parts[:2] == ["ultimaker", "3"]: - Logger.log("i", "You have a UM3 in printer list. Let's check the firmware!") + machine_id = self.MACHINE_PER_NAME.get(machine_name.lower()) + if machine_id is not None: + Logger.log("i", "You have a {0} in the printer list. Let's check the firmware!".format(machine_name)) - # Nothing to parse, just get the string - # TODO: In the future may be done by parsing a JSON file with diferent version for each printer model - current_version = reader(current_version_file).readline().rstrip() + current_version = self.getCurrentVersionForMachine(machine_id) # If it is the first time the version is checked, the checked_version is '' - checked_version = Application.getInstance().getPreferences().getValue("info/latest_checked_firmware") + setting_key_str = get_settings_key_for_machine(machine_id) + checked_version = Application.getInstance().getPreferences().getValue(setting_key_str) # If the checked_version is '', it's because is the first time we check firmware and in this case # we will not show the notification, but we will store it for the next time - Application.getInstance().getPreferences().setValue("info/latest_checked_firmware", current_version) + Application.getInstance().getPreferences().setValue(setting_key_str, current_version) Logger.log("i", "Reading firmware version of %s: checked = %s - latest = %s", machine_name, checked_version, current_version) # The first time we want to store the current version, the notification will not be shown, @@ -78,12 +136,17 @@ class FirmwareUpdateCheckerJob(Job): button_style=Message.ActionButtonStyle.LINK, button_align=Message.ActionButtonStyle.BUTTON_ALIGN_LEFT) - # If we do this in a cool way, the download url should be available in the JSON file if self._set_download_url_callback: - self._set_download_url_callback("https://ultimaker.com/en/resources/20500-upgrade-firmware") + redirect = self.REDIRECT_USER_PER_MACHINE.get(machine_id) + if redirect is not None: + self._set_download_url_callback(redirect) + else: + Logger.log('w', "No callback-url for firmware of {0}".format(repr(machine_id))) message.actionTriggered.connect(self._callback) message.show() + else: + Logger.log('i', "No machine with name {0} in list of firmware to check.".format(repr(machine_id))) except Exception as e: Logger.log("w", "Failed to check for new version: %s", e) From 487ef52c6616b8f15af9d7cd3140eaff5e46fbcb Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 10 Oct 2018 16:52:47 +0200 Subject: [PATCH 03/31] Warn on error and continue on encountering 'future-proof' (now) or old (later) version-URLs. --- .../FirmwareUpdateCheckerJob.py | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index 658e820b4b..40546d4a05 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -10,6 +10,7 @@ from UM.Job import Job from UM.Version import Version import urllib.request +from urllib.error import URLError import codecs from UM.i18n import i18nCatalog @@ -62,16 +63,20 @@ class FirmwareUpdateCheckerJob(Job): self._callback = callback self._set_download_url_callback = set_download_url_callback - application_name = Application.getInstance().getApplicationName() - application_version = Application.getInstance().getVersion() - self._headers = {"User-Agent": "%s - %s" % (application_name, application_version)} + self._headers = {} # Don't set headers yet. def getUrlResponse(self, url: str) -> str: - request = urllib.request.Request(url, headers=self._headers) - current_version_file = urllib.request.urlopen(request) - reader = codecs.getreader("utf-8") + result = "0.0.0" - return reader(current_version_file).read(firstline=True) + try: + request = urllib.request.Request(url, headers=self._headers) + current_version_file = urllib.request.urlopen(request) + reader = codecs.getreader("utf-8") + result = reader(current_version_file).read(firstline=True) + except URLError: + Logger.log('w', "Could not reach '{0}', if this URL is old, consider removal.".format(url)) + + return result def getCurrentVersionForMachine(self, machine_id: MachineId) -> Version: max_version = Version([0, 0, 0]) @@ -95,6 +100,10 @@ class FirmwareUpdateCheckerJob(Job): return try: + application_name = Application.getInstance().getApplicationName() + application_version = Application.getInstance().getVersion() + self._headers = {"User-Agent": "%s - %s" % (application_name, application_version)} + # get machine name from the definition container machine_name = self._container.definition.getName() machine_name_parts = machine_name.lower().split(" ") From d8ed3d607403e34205dd46da300f2feaef82b2b0 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 11 Oct 2018 14:56:07 +0200 Subject: [PATCH 04/31] Check the whole list for firmware-updates instead of just the first added container. --- plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py | 7 ++++--- plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py index 80a954c1cc..459d29265d 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py @@ -49,6 +49,7 @@ class FirmwareUpdateChecker(Extension): self._download_url = None self._check_job = None + self._name_cache = [] ## Callback for the message that is spawned when there is a new version. def _onActionTriggered(self, message, action): @@ -74,10 +75,10 @@ class FirmwareUpdateChecker(Extension): # \param silent type(boolean) Suppresses messages other than "new version found" messages. # This is used when checking for a new firmware version at startup. def checkFirmwareVersion(self, container = None, silent = False): - # Do not run multiple check jobs in parallel - if self._check_job is not None: - Logger.log("i", "A firmware update check is already running, do nothing.") + container_name = container.definition.getName() + if container_name in self._name_cache: return + self._name_cache.append(container_name) self._check_job = FirmwareUpdateCheckerJob(container = container, silent = silent, urls = self.VERSION_URLS_PER_MACHINE, diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index 40546d4a05..14a40e3cce 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -34,6 +34,7 @@ def default_parse_version_response(response: str) -> Version: ## This job checks if there is an update available on the provided URL. + class FirmwareUpdateCheckerJob(Job): MACHINE_PER_NAME = \ { @@ -155,7 +156,7 @@ class FirmwareUpdateCheckerJob(Job): message.actionTriggered.connect(self._callback) message.show() else: - Logger.log('i', "No machine with name {0} in list of firmware to check.".format(repr(machine_id))) + Logger.log('i', "No machine with name {0} in list of firmware to check.".format(machine_name)) except Exception as e: Logger.log("w", "Failed to check for new version: %s", e) From 12999f48c848359e6ed33d8997bb9c193a63ae30 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 11 Oct 2018 15:27:04 +0200 Subject: [PATCH 05/31] FirmwareUpdateCheckerJob: Move introduced hardcoded values to static variables. --- .../FirmwareUpdateCheckerJob.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index 14a40e3cce..41710e7e86 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -34,8 +34,11 @@ def default_parse_version_response(response: str) -> Version: ## This job checks if there is an update available on the provided URL. - class FirmwareUpdateCheckerJob(Job): + STRING_ZERO_VERSION = "0.0.0" + STRING_EPSILON_VERSION = "0.0.1" + ZERO_VERSION = Version(STRING_ZERO_VERSION) + EPSILON_VERSION = Version(STRING_EPSILON_VERSION) MACHINE_PER_NAME = \ { "ultimaker 3": MachineId.UM3, @@ -67,7 +70,7 @@ class FirmwareUpdateCheckerJob(Job): self._headers = {} # Don't set headers yet. def getUrlResponse(self, url: str) -> str: - result = "0.0.0" + result = self.STRING_ZERO_VERSION try: request = urllib.request.Request(url, headers=self._headers) @@ -80,7 +83,7 @@ class FirmwareUpdateCheckerJob(Job): return result def getCurrentVersionForMachine(self, machine_id: MachineId) -> Version: - max_version = Version([0, 0, 0]) + max_version = self.ZERO_VERSION machine_urls = self._urls.get(machine_id) parse_function = self.PARSE_VERSION_URL_PER_MACHINE.get(machine_id) @@ -90,7 +93,7 @@ class FirmwareUpdateCheckerJob(Job): if version > max_version: max_version = version - if max_version < Version([0, 0, 1]): + if max_version < self.EPSILON_VERSION: Logger.log('w', "MachineID {0} not handled!".format(repr(machine_id))) return max_version @@ -107,7 +110,6 @@ class FirmwareUpdateCheckerJob(Job): # get machine name from the definition container machine_name = self._container.definition.getName() - machine_name_parts = machine_name.lower().split(" ") # If it is not None, then we compare between the checked_version and the current_version machine_id = self.MACHINE_PER_NAME.get(machine_name.lower()) @@ -118,7 +120,7 @@ class FirmwareUpdateCheckerJob(Job): # If it is the first time the version is checked, the checked_version is '' setting_key_str = get_settings_key_for_machine(machine_id) - checked_version = Application.getInstance().getPreferences().getValue(setting_key_str) + checked_version = Version(Application.getInstance().getPreferences().getValue(setting_key_str)) # If the checked_version is '', it's because is the first time we check firmware and in this case # we will not show the notification, but we will store it for the next time From 6c2791f38240992465014969b7d0fb9c6335dfa6 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 11 Oct 2018 17:16:01 +0200 Subject: [PATCH 06/31] Parse the firmware-update-check lookup-tables from a (new) .json instead of hardcoded. --- .../FirmwareUpdateChecker.py | 47 +++++++----- .../FirmwareUpdateCheckerJob.py | 73 ++++++++++--------- .../resources/machines.json | 36 +++++++++ 3 files changed, 101 insertions(+), 55 deletions(-) create mode 100644 plugins/FirmwareUpdateChecker/resources/machines.json diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py index 459d29265d..1736bb228a 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py @@ -1,18 +1,20 @@ # Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +import json, os from PyQt5.QtCore import QUrl from PyQt5.QtGui import QDesktopServices from UM.Extension import Extension from UM.Application import Application from UM.Logger import Logger +from UM.PluginRegistry import PluginRegistry from UM.i18n import i18nCatalog from UM.Settings.ContainerRegistry import ContainerRegistry from cura.Settings.GlobalStack import GlobalStack -from .FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob, MachineId, get_settings_key_for_machine +from .FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob, get_settings_key_for_machine i18n_catalog = i18nCatalog("cura") @@ -20,38 +22,23 @@ i18n_catalog = i18nCatalog("cura") # The plugin is currently only usable for applications maintained by Ultimaker. But it should be relatively easy # to change it to work for other applications. class FirmwareUpdateChecker(Extension): - JEDI_VERSION_URL = "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources" - UM_NEW_URL_TEMPLATE = "http://software.ultimaker.com/releases/firmware/{0}/stable/version.txt" - VERSION_URLS_PER_MACHINE = \ - { - MachineId.UM3: [JEDI_VERSION_URL, UM_NEW_URL_TEMPLATE.format(MachineId.UM3.value)], - MachineId.UM3E: [JEDI_VERSION_URL, UM_NEW_URL_TEMPLATE.format(MachineId.UM3E.value)], - MachineId.S5: [UM_NEW_URL_TEMPLATE.format(MachineId.S5.value)] - } - # The 'new'-style URL is the only way to check for S5 firmware, - # and in the future, the UM3 line will also switch over, but for now the old 'JEDI'-style URL is still needed. - # TODO: Parse all of that from a file, because this will be a big mess of large static values which gets worse with each printer. - # See also the to-do in FirmWareCheckerJob. def __init__(self): super().__init__() - # Initialize the Preference called `latest_checked_firmware` that stores the last version - # checked for each printer. - for machine_id in MachineId: - Application.getInstance().getPreferences().addPreference(get_settings_key_for_machine(machine_id), "") - # Listen to a Signal that indicates a change in the list of printers, just if the user has enabled the # 'check for updates' option Application.getInstance().getPreferences().addPreference("info/automatic_update_check", True) if Application.getInstance().getPreferences().getValue("info/automatic_update_check"): ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded) + self._late_init = True # Init some things after creation, since we need the path from the plugin-mgr. self._download_url = None self._check_job = None self._name_cache = [] ## Callback for the message that is spawned when there is a new version. + # TODO: Set the right download URL for each message! def _onActionTriggered(self, message, action): if action == "download": if self._download_url is not None: @@ -68,6 +55,25 @@ class FirmwareUpdateChecker(Extension): def _onJobFinished(self, *args, **kwargs): self._check_job = None + def lateInit(self): + self._late_init = False + + # Open the .json file with the needed lookup-lists for each machine(/model) and retrieve 'raw' json. + self._machines_json = None + json_path = os.path.join(PluginRegistry.getInstance().getPluginPath("FirmwareUpdateChecker"), + "resources/machines.json") + with open(json_path, "r", encoding="utf-8") as json_file: + self._machines_json = json.load(json_file).get("machines") + if self._machines_json is None: + Logger.log('e', "Missing or inaccessible: {0}".format(json_path)) + return + + # Initialize the Preference called `latest_checked_firmware` that stores the last version + # checked for each printer. + for machine_json in self._machines_json: + machine_id = machine_json.get("id") + Application.getInstance().getPreferences().addPreference(get_settings_key_for_machine(machine_id), "") + ## Connect with software.ultimaker.com, load latest.version and check version info. # If the version info is different from the current version, spawn a message to # allow the user to download it. @@ -75,13 +81,16 @@ class FirmwareUpdateChecker(Extension): # \param silent type(boolean) Suppresses messages other than "new version found" messages. # This is used when checking for a new firmware version at startup. def checkFirmwareVersion(self, container = None, silent = False): + if self._late_init: + self.lateInit() + container_name = container.definition.getName() if container_name in self._name_cache: return self._name_cache.append(container_name) self._check_job = FirmwareUpdateCheckerJob(container = container, silent = silent, - urls = self.VERSION_URLS_PER_MACHINE, + machines_json = self._machines_json, callback = self._onActionTriggered, set_download_url_callback = self._onSetDownloadUrl) self._check_job.start() diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index 41710e7e86..336b954f5e 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -16,16 +16,9 @@ import codecs from UM.i18n import i18nCatalog i18n_catalog = i18nCatalog("cura") -# For UM-machines, these need to match the unique firmware-ID (also used in the URLs), i.o.t. only define in one place. -@unique -class MachineId(Enum): - UM3 = 9066 - UM3E = 9511 - S5 = 9051 - -def get_settings_key_for_machine(machine_id: MachineId) -> str: - return "info/latest_checked_firmware_for_{0}".format(machine_id.value) +def get_settings_key_for_machine(machine_id: int) -> str: + return "info/latest_checked_firmware_for_{0}".format(machine_id) def default_parse_version_response(response: str) -> Version: @@ -39,31 +32,39 @@ class FirmwareUpdateCheckerJob(Job): STRING_EPSILON_VERSION = "0.0.1" ZERO_VERSION = Version(STRING_ZERO_VERSION) EPSILON_VERSION = Version(STRING_EPSILON_VERSION) - MACHINE_PER_NAME = \ - { - "ultimaker 3": MachineId.UM3, - "ultimaker 3 extended": MachineId.UM3E, - "ultimaker s5": MachineId.S5 - } - PARSE_VERSION_URL_PER_MACHINE = \ - { - MachineId.UM3: default_parse_version_response, - MachineId.UM3E: default_parse_version_response, - MachineId.S5: default_parse_version_response - } - REDIRECT_USER_PER_MACHINE = \ - { - MachineId.UM3: "https://ultimaker.com/en/resources/20500-upgrade-firmware", - MachineId.UM3E: "https://ultimaker.com/en/resources/20500-upgrade-firmware", - MachineId.S5: "https://ultimaker.com/en/resources/20500-upgrade-firmware" - } - # TODO: Parse all of that from a file, because this will be a big mess of large static values which gets worse with each printer. + JSON_NAME_TO_VERSION_PARSE_FUNCTION = {"default": default_parse_version_response} - def __init__(self, container=None, silent=False, urls=None, callback=None, set_download_url_callback=None): + def __init__(self, container=None, silent=False, machines_json=None, callback=None, set_download_url_callback=None): super().__init__() self._container = container self.silent = silent - self._urls = urls + + # Parse all the needed lookup-tables from the '.json' file(s) in the resources folder. + # TODO: This should not be here when the merge to master is done, as it will be repeatedly recreated. + # It should be a separate object this constructor receives instead. + self._machine_ids = [] + self._machine_per_name = {} + self._parse_version_url_per_machine = {} + self._check_urls_per_machine = {} + self._redirect_user_per_machine = {} + try: + for machine_json in machines_json: + machine_id = machine_json.get("id") + machine_name = machine_json.get("name") + self._machine_ids.append(machine_id) + self._machine_per_name[machine_name] = machine_id + version_parse_function = self.JSON_NAME_TO_VERSION_PARSE_FUNCTION.get(machine_json.get("version_parser")) + if version_parse_function is None: + Logger.log('w', "No version-parse-function specified for machine {0}.".format(machine_name)) + version_parse_function = default_parse_version_response # Use default instead if nothing is found. + self._parse_version_url_per_machine[machine_id] = version_parse_function + self._check_urls_per_machine[machine_id] = [] # Multiple check-urls: see '_comment' in the .json file. + for check_url in machine_json.get("check_urls"): + self._check_urls_per_machine[machine_id].append(check_url) + self._redirect_user_per_machine[machine_id] = machine_json.get("update_url") + except: + Logger.log('e', "Couldn't parse firmware-update-check loopup-lists from file.") + self._callback = callback self._set_download_url_callback = set_download_url_callback @@ -82,11 +83,11 @@ class FirmwareUpdateCheckerJob(Job): return result - def getCurrentVersionForMachine(self, machine_id: MachineId) -> Version: + def getCurrentVersionForMachine(self, machine_id: int) -> Version: max_version = self.ZERO_VERSION - machine_urls = self._urls.get(machine_id) - parse_function = self.PARSE_VERSION_URL_PER_MACHINE.get(machine_id) + machine_urls = self._check_urls_per_machine.get(machine_id) + parse_function = self._parse_version_url_per_machine.get(machine_id) if machine_urls is not None and parse_function is not None: for url in machine_urls: version = parse_function(self.getUrlResponse(url)) @@ -99,7 +100,7 @@ class FirmwareUpdateCheckerJob(Job): return max_version def run(self): - if not self._urls or self._urls is None: + if not self._machine_ids or self._machine_ids is None: Logger.log("e", "Can not check for a new release. URL not set!") return @@ -112,7 +113,7 @@ class FirmwareUpdateCheckerJob(Job): machine_name = self._container.definition.getName() # If it is not None, then we compare between the checked_version and the current_version - machine_id = self.MACHINE_PER_NAME.get(machine_name.lower()) + machine_id = self._machine_per_name.get(machine_name.lower()) if machine_id is not None: Logger.log("i", "You have a {0} in the printer list. Let's check the firmware!".format(machine_name)) @@ -150,7 +151,7 @@ class FirmwareUpdateCheckerJob(Job): # If we do this in a cool way, the download url should be available in the JSON file if self._set_download_url_callback: - redirect = self.REDIRECT_USER_PER_MACHINE.get(machine_id) + redirect = self._redirect_user_per_machine.get(machine_id) if redirect is not None: self._set_download_url_callback(redirect) else: diff --git a/plugins/FirmwareUpdateChecker/resources/machines.json b/plugins/FirmwareUpdateChecker/resources/machines.json new file mode 100644 index 0000000000..5dc9aadbbf --- /dev/null +++ b/plugins/FirmwareUpdateChecker/resources/machines.json @@ -0,0 +1,36 @@ +{ + "_comment": "Multiple 'update_urls': The 'new'-style URL is the only way to check for S5 firmware, and in the future, the UM3 line will also switch over, but for now the old 'JEDI'-style URL is still needed.", + + "machines": + [ + { + "id": 9066, + "name": "ultimaker 3", + "check_urls": + [ + "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources", + "http://software.ultimaker.com/releases/firmware/9066/stable/version.txt" + ], + "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware", + "version_parser": "default" + }, + { + "id": 9511, + "name": "ultimaker 3 extended", + "check_urls": + [ + "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources", + "http://software.ultimaker.com/releases/firmware/9511/stable/version.txt" + ], + "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware", + "version_parser": "default" + }, + { + "id": 9051, + "name": "ultimaker s5", + "check_urls": ["http://software.ultimaker.com/releases/firmware/9051/stable/version.txt"], + "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware", + "version_parser": "default" + } + ] +} From 472d012c08f8c28a8eba29cf65baa50fa802aeda Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 11 Oct 2018 17:52:06 +0200 Subject: [PATCH 07/31] Move firmware-update-checker json-parsing to its own class (also don't repeat parsing each time). --- .../FirmwareUpdateChecker.py | 17 ++--- .../FirmwareUpdateCheckerJob.py | 51 +++----------- .../FirmwareUpdateCheckerLookup.py | 67 +++++++++++++++++++ 3 files changed, 81 insertions(+), 54 deletions(-) create mode 100644 plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py index 1736bb228a..223cf2d433 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py @@ -15,6 +15,7 @@ from UM.Settings.ContainerRegistry import ContainerRegistry from cura.Settings.GlobalStack import GlobalStack from .FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob, get_settings_key_for_machine +from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup i18n_catalog = i18nCatalog("cura") @@ -58,20 +59,12 @@ class FirmwareUpdateChecker(Extension): def lateInit(self): self._late_init = False - # Open the .json file with the needed lookup-lists for each machine(/model) and retrieve 'raw' json. - self._machines_json = None - json_path = os.path.join(PluginRegistry.getInstance().getPluginPath("FirmwareUpdateChecker"), - "resources/machines.json") - with open(json_path, "r", encoding="utf-8") as json_file: - self._machines_json = json.load(json_file).get("machines") - if self._machines_json is None: - Logger.log('e', "Missing or inaccessible: {0}".format(json_path)) - return + self._lookups = FirmwareUpdateCheckerLookup(os.path.join(PluginRegistry.getInstance().getPluginPath( + "FirmwareUpdateChecker"), "resources/machines.json")) # Initialize the Preference called `latest_checked_firmware` that stores the last version # checked for each printer. - for machine_json in self._machines_json: - machine_id = machine_json.get("id") + for machine_id in self._lookups.getMachineIds(): Application.getInstance().getPreferences().addPreference(get_settings_key_for_machine(machine_id), "") ## Connect with software.ultimaker.com, load latest.version and check version info. @@ -90,7 +83,7 @@ class FirmwareUpdateChecker(Extension): self._name_cache.append(container_name) self._check_job = FirmwareUpdateCheckerJob(container = container, silent = silent, - machines_json = self._machines_json, + lookups = self._lookups, callback = self._onActionTriggered, set_download_url_callback = self._onSetDownloadUrl) self._check_job.start() diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index 336b954f5e..6d72e130b2 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -1,8 +1,6 @@ # Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from enum import Enum, unique - from UM.Application import Application from UM.Message import Message from UM.Logger import Logger @@ -13,6 +11,8 @@ import urllib.request from urllib.error import URLError import codecs +from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup + from UM.i18n import i18nCatalog i18n_catalog = i18nCatalog("cura") @@ -21,53 +21,20 @@ def get_settings_key_for_machine(machine_id: int) -> str: return "info/latest_checked_firmware_for_{0}".format(machine_id) -def default_parse_version_response(response: str) -> Version: - raw_str = response.split('\n', 1)[0].rstrip() - return Version(raw_str.split('.')) # Split it into a list; the default parsing of 'single string' is different. - - ## This job checks if there is an update available on the provided URL. class FirmwareUpdateCheckerJob(Job): STRING_ZERO_VERSION = "0.0.0" STRING_EPSILON_VERSION = "0.0.1" ZERO_VERSION = Version(STRING_ZERO_VERSION) EPSILON_VERSION = Version(STRING_EPSILON_VERSION) - JSON_NAME_TO_VERSION_PARSE_FUNCTION = {"default": default_parse_version_response} - def __init__(self, container=None, silent=False, machines_json=None, callback=None, set_download_url_callback=None): + def __init__(self, container=None, silent=False, lookups:FirmwareUpdateCheckerLookup=None, callback=None, set_download_url_callback=None): super().__init__() self._container = container self.silent = silent - - # Parse all the needed lookup-tables from the '.json' file(s) in the resources folder. - # TODO: This should not be here when the merge to master is done, as it will be repeatedly recreated. - # It should be a separate object this constructor receives instead. - self._machine_ids = [] - self._machine_per_name = {} - self._parse_version_url_per_machine = {} - self._check_urls_per_machine = {} - self._redirect_user_per_machine = {} - try: - for machine_json in machines_json: - machine_id = machine_json.get("id") - machine_name = machine_json.get("name") - self._machine_ids.append(machine_id) - self._machine_per_name[machine_name] = machine_id - version_parse_function = self.JSON_NAME_TO_VERSION_PARSE_FUNCTION.get(machine_json.get("version_parser")) - if version_parse_function is None: - Logger.log('w', "No version-parse-function specified for machine {0}.".format(machine_name)) - version_parse_function = default_parse_version_response # Use default instead if nothing is found. - self._parse_version_url_per_machine[machine_id] = version_parse_function - self._check_urls_per_machine[machine_id] = [] # Multiple check-urls: see '_comment' in the .json file. - for check_url in machine_json.get("check_urls"): - self._check_urls_per_machine[machine_id].append(check_url) - self._redirect_user_per_machine[machine_id] = machine_json.get("update_url") - except: - Logger.log('e', "Couldn't parse firmware-update-check loopup-lists from file.") - self._callback = callback self._set_download_url_callback = set_download_url_callback - + self._lookups = lookups self._headers = {} # Don't set headers yet. def getUrlResponse(self, url: str) -> str: @@ -86,8 +53,8 @@ class FirmwareUpdateCheckerJob(Job): def getCurrentVersionForMachine(self, machine_id: int) -> Version: max_version = self.ZERO_VERSION - machine_urls = self._check_urls_per_machine.get(machine_id) - parse_function = self._parse_version_url_per_machine.get(machine_id) + machine_urls = self._lookups.getCheckUrlsFor(machine_id) + parse_function = self._lookups.getParseVersionUrlFor(machine_id) if machine_urls is not None and parse_function is not None: for url in machine_urls: version = parse_function(self.getUrlResponse(url)) @@ -100,7 +67,7 @@ class FirmwareUpdateCheckerJob(Job): return max_version def run(self): - if not self._machine_ids or self._machine_ids is None: + if self._lookups is None: Logger.log("e", "Can not check for a new release. URL not set!") return @@ -113,7 +80,7 @@ class FirmwareUpdateCheckerJob(Job): machine_name = self._container.definition.getName() # If it is not None, then we compare between the checked_version and the current_version - machine_id = self._machine_per_name.get(machine_name.lower()) + machine_id = self._lookups.getMachineByName(machine_name.lower()) if machine_id is not None: Logger.log("i", "You have a {0} in the printer list. Let's check the firmware!".format(machine_name)) @@ -151,7 +118,7 @@ class FirmwareUpdateCheckerJob(Job): # If we do this in a cool way, the download url should be available in the JSON file if self._set_download_url_callback: - redirect = self._redirect_user_per_machine.get(machine_id) + redirect = self._lookups.getRedirectUseror(machine_id) if redirect is not None: self._set_download_url_callback(redirect) else: diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py new file mode 100644 index 0000000000..62d43553c1 --- /dev/null +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py @@ -0,0 +1,67 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +import json, os + +from UM.Logger import Logger +from UM.Version import Version + +from UM.i18n import i18nCatalog +i18n_catalog = i18nCatalog("cura") + +def default_parse_version_response(response: str) -> Version: + raw_str = response.split('\n', 1)[0].rstrip() + return Version(raw_str.split('.')) # Split it into a list; the default parsing of 'single string' is different. + + +class FirmwareUpdateCheckerLookup: + JSON_NAME_TO_VERSION_PARSE_FUNCTION = {"default": default_parse_version_response} + + def __init__(self, json_path): + # Open the .json file with the needed lookup-lists for each machine(/model) and retrieve 'raw' json. + machines_json = None + with open(json_path, "r", encoding="utf-8") as json_file: + machines_json = json.load(json_file).get("machines") + if machines_json is None: + Logger.log('e', "Missing or inaccessible: {0}".format(json_path)) + return + + # Parse all the needed lookup-tables from the '.json' file(s) in the resources folder. + self._machine_ids = [] + self._machine_per_name = {} + self._parse_version_url_per_machine = {} + self._check_urls_per_machine = {} + self._redirect_user_per_machine = {} + try: + for machine_json in machines_json: + machine_id = machine_json.get("id") + machine_name = machine_json.get("name") + self._machine_ids.append(machine_id) + self._machine_per_name[machine_name] = machine_id + version_parse_function = \ + self.JSON_NAME_TO_VERSION_PARSE_FUNCTION.get(machine_json.get("version_parser")) + if version_parse_function is None: + Logger.log('w', "No version-parse-function specified for machine {0}.".format(machine_name)) + version_parse_function = default_parse_version_response # Use default instead if nothing is found. + self._parse_version_url_per_machine[machine_id] = version_parse_function + self._check_urls_per_machine[machine_id] = [] # Multiple check-urls: see '_comment' in the .json file. + for check_url in machine_json.get("check_urls"): + self._check_urls_per_machine[machine_id].append(check_url) + self._redirect_user_per_machine[machine_id] = machine_json.get("update_url") + except: + Logger.log('e', "Couldn't parse firmware-update-check loopup-lists from file.") + + def getMachineIds(self) -> [int]: + return self._machine_ids + + def getMachineByName(self, machine_name: str) -> int: + return self._machine_per_name.get(machine_name) + + def getParseVersionUrlFor(self, machine_id: int) -> str: + return self._parse_version_url_per_machine.get(machine_id) + + def getCheckUrlsFor(self, machine_id: int) -> [str]: + return self._check_urls_per_machine.get(machine_id) + + def getRedirectUseror(self, machine_id: int) -> str: + return self._redirect_user_per_machine.get(machine_id) From 4ecac6e27f71b9b8e6cd65aa8e96cc816c7e5428 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 11 Oct 2018 18:24:07 +0200 Subject: [PATCH 08/31] Set the right firmware-download-URL in the actual update-firmware-message. --- .../FirmwareUpdateChecker.py | 26 +++++++++---------- .../FirmwareUpdateCheckerJob.py | 19 +++----------- .../FirmwareUpdateCheckerLookup.py | 7 ++++- 3 files changed, 23 insertions(+), 29 deletions(-) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py index 223cf2d433..90590fc5a2 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py @@ -14,8 +14,8 @@ from UM.Settings.ContainerRegistry import ContainerRegistry from cura.Settings.GlobalStack import GlobalStack -from .FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob, get_settings_key_for_machine -from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup +from .FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob +from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup, get_settings_key_for_machine i18n_catalog = i18nCatalog("cura") @@ -39,14 +39,15 @@ class FirmwareUpdateChecker(Extension): self._name_cache = [] ## Callback for the message that is spawned when there is a new version. - # TODO: Set the right download URL for each message! def _onActionTriggered(self, message, action): - if action == "download": - if self._download_url is not None: - QDesktopServices.openUrl(QUrl(self._download_url)) - - def _onSetDownloadUrl(self, download_url): - self._download_url = download_url + try: + download_url = self._lookups.getRedirectUserFor(int(action)) + if download_url is not None: + QDesktopServices.openUrl(QUrl(download_url)) + else: + Logger.log('e', "Can't find URL for {0}".format(action)) + except: + Logger.log('e', "Don't know what to do with {0}".format(action)) def _onContainerAdded(self, container): # Only take care when a new GlobalStack was added @@ -56,7 +57,7 @@ class FirmwareUpdateChecker(Extension): def _onJobFinished(self, *args, **kwargs): self._check_job = None - def lateInit(self): + def doLateInit(self): self._late_init = False self._lookups = FirmwareUpdateCheckerLookup(os.path.join(PluginRegistry.getInstance().getPluginPath( @@ -75,7 +76,7 @@ class FirmwareUpdateChecker(Extension): # This is used when checking for a new firmware version at startup. def checkFirmwareVersion(self, container = None, silent = False): if self._late_init: - self.lateInit() + self.doLateInit() container_name = container.definition.getName() if container_name in self._name_cache: @@ -84,7 +85,6 @@ class FirmwareUpdateChecker(Extension): self._check_job = FirmwareUpdateCheckerJob(container = container, silent = silent, lookups = self._lookups, - callback = self._onActionTriggered, - set_download_url_callback = self._onSetDownloadUrl) + callback = self._onActionTriggered) self._check_job.start() self._check_job.finished.connect(self._onJobFinished) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index 6d72e130b2..342287ca76 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -11,16 +11,12 @@ import urllib.request from urllib.error import URLError import codecs -from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup +from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup, get_settings_key_for_machine from UM.i18n import i18nCatalog i18n_catalog = i18nCatalog("cura") -def get_settings_key_for_machine(machine_id: int) -> str: - return "info/latest_checked_firmware_for_{0}".format(machine_id) - - ## This job checks if there is an update available on the provided URL. class FirmwareUpdateCheckerJob(Job): STRING_ZERO_VERSION = "0.0.0" @@ -28,12 +24,12 @@ class FirmwareUpdateCheckerJob(Job): ZERO_VERSION = Version(STRING_ZERO_VERSION) EPSILON_VERSION = Version(STRING_EPSILON_VERSION) - def __init__(self, container=None, silent=False, lookups:FirmwareUpdateCheckerLookup=None, callback=None, set_download_url_callback=None): + def __init__(self, container=None, silent=False, lookups:FirmwareUpdateCheckerLookup=None, callback=None): super().__init__() self._container = container self.silent = silent self._callback = callback - self._set_download_url_callback = set_download_url_callback + self._lookups = lookups self._headers = {} # Don't set headers yet. @@ -109,20 +105,13 @@ class FirmwareUpdateCheckerJob(Job): "@info:title The %s gets replaced with the printer name.", "New %s firmware available") % machine_name) - message.addAction("download", + message.addAction(machine_id, i18n_catalog.i18nc("@action:button", "How to update"), "[no_icon]", "[no_description]", button_style=Message.ActionButtonStyle.LINK, button_align=Message.ActionButtonStyle.BUTTON_ALIGN_LEFT) - # If we do this in a cool way, the download url should be available in the JSON file - if self._set_download_url_callback: - redirect = self._lookups.getRedirectUseror(machine_id) - if redirect is not None: - self._set_download_url_callback(redirect) - else: - Logger.log('w', "No callback-url for firmware of {0}".format(repr(machine_id))) message.actionTriggered.connect(self._callback) message.show() else: diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py index 62d43553c1..f2c9082f76 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py @@ -9,6 +9,11 @@ from UM.Version import Version from UM.i18n import i18nCatalog i18n_catalog = i18nCatalog("cura") + +def get_settings_key_for_machine(machine_id: int) -> str: + return "info/latest_checked_firmware_for_{0}".format(machine_id) + + def default_parse_version_response(response: str) -> Version: raw_str = response.split('\n', 1)[0].rstrip() return Version(raw_str.split('.')) # Split it into a list; the default parsing of 'single string' is different. @@ -63,5 +68,5 @@ class FirmwareUpdateCheckerLookup: def getCheckUrlsFor(self, machine_id: int) -> [str]: return self._check_urls_per_machine.get(machine_id) - def getRedirectUseror(self, machine_id: int) -> str: + def getRedirectUserFor(self, machine_id: int) -> str: return self._redirect_user_per_machine.get(machine_id) From f2b50c748c1aea35e61de119fb3a08a28afdb295 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 11 Oct 2018 21:54:27 +0200 Subject: [PATCH 09/31] Fix typing in the FirmwareUpdateChecker plugin. --- .../FirmwareUpdateChecker.py | 14 +++++---- .../FirmwareUpdateCheckerJob.py | 5 ++-- .../FirmwareUpdateCheckerLookup.py | 30 ++++++++++--------- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py index 90590fc5a2..71bdd0bc23 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py @@ -1,10 +1,12 @@ # Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -import json, os +import os from PyQt5.QtCore import QUrl from PyQt5.QtGui import QDesktopServices +from typing import List + from UM.Extension import Extension from UM.Application import Application from UM.Logger import Logger @@ -19,12 +21,13 @@ from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup, get_settin i18n_catalog = i18nCatalog("cura") + ## This Extension checks for new versions of the firmware based on the latest checked version number. # The plugin is currently only usable for applications maintained by Ultimaker. But it should be relatively easy # to change it to work for other applications. class FirmwareUpdateChecker(Extension): - def __init__(self): + def __init__(self) -> None: super().__init__() # Listen to a Signal that indicates a change in the list of printers, just if the user has enabled the @@ -36,7 +39,8 @@ class FirmwareUpdateChecker(Extension): self._late_init = True # Init some things after creation, since we need the path from the plugin-mgr. self._download_url = None self._check_job = None - self._name_cache = [] + self._name_cache = [] # type: List[str] + self._lookups = None ## Callback for the message that is spawned when there is a new version. def _onActionTriggered(self, message, action): @@ -46,8 +50,8 @@ class FirmwareUpdateChecker(Extension): QDesktopServices.openUrl(QUrl(download_url)) else: Logger.log('e', "Can't find URL for {0}".format(action)) - except: - Logger.log('e', "Don't know what to do with {0}".format(action)) + except Exception as ex: + Logger.log('e', "Don't know what to do with '{0}' because {1}".format(action, ex)) def _onContainerAdded(self, container): # Only take care when a new GlobalStack was added diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index 342287ca76..d186cbb4e4 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -9,6 +9,7 @@ from UM.Version import Version import urllib.request from urllib.error import URLError +from typing import Dict import codecs from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup, get_settings_key_for_machine @@ -24,14 +25,14 @@ class FirmwareUpdateCheckerJob(Job): ZERO_VERSION = Version(STRING_ZERO_VERSION) EPSILON_VERSION = Version(STRING_EPSILON_VERSION) - def __init__(self, container=None, silent=False, lookups:FirmwareUpdateCheckerLookup=None, callback=None): + def __init__(self, container, silent, lookups: FirmwareUpdateCheckerLookup, callback) -> None: super().__init__() self._container = container self.silent = silent self._callback = callback self._lookups = lookups - self._headers = {} # Don't set headers yet. + self._headers = {} # type:Dict[str, str] # Don't set headers yet. def getUrlResponse(self, url: str) -> str: result = self.STRING_ZERO_VERSION diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py index f2c9082f76..f6d7a24da0 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py @@ -1,7 +1,9 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -import json, os +import json + +from typing import Callable, Dict, List, Optional from UM.Logger import Logger from UM.Version import Version @@ -22,7 +24,7 @@ def default_parse_version_response(response: str) -> Version: class FirmwareUpdateCheckerLookup: JSON_NAME_TO_VERSION_PARSE_FUNCTION = {"default": default_parse_version_response} - def __init__(self, json_path): + def __init__(self, json_path) -> None: # Open the .json file with the needed lookup-lists for each machine(/model) and retrieve 'raw' json. machines_json = None with open(json_path, "r", encoding="utf-8") as json_file: @@ -32,11 +34,11 @@ class FirmwareUpdateCheckerLookup: return # Parse all the needed lookup-tables from the '.json' file(s) in the resources folder. - self._machine_ids = [] - self._machine_per_name = {} - self._parse_version_url_per_machine = {} - self._check_urls_per_machine = {} - self._redirect_user_per_machine = {} + self._machine_ids = [] # type:List[int] + self._machine_per_name = {} # type:Dict[str, int] + self._parse_version_url_per_machine = {} # type:Dict[int, Callable] + self._check_urls_per_machine = {} # type:Dict[int, List[str]] + self._redirect_user_per_machine = {} # type:Dict[int, str] try: for machine_json in machines_json: machine_id = machine_json.get("id") @@ -53,20 +55,20 @@ class FirmwareUpdateCheckerLookup: for check_url in machine_json.get("check_urls"): self._check_urls_per_machine[machine_id].append(check_url) self._redirect_user_per_machine[machine_id] = machine_json.get("update_url") - except: - Logger.log('e', "Couldn't parse firmware-update-check loopup-lists from file.") + except Exception as ex: + Logger.log('e', "Couldn't parse firmware-update-check loopup-lists from file because {0}.".format(ex)) - def getMachineIds(self) -> [int]: + def getMachineIds(self) -> List[int]: return self._machine_ids - def getMachineByName(self, machine_name: str) -> int: + def getMachineByName(self, machine_name: str) -> Optional[int]: return self._machine_per_name.get(machine_name) - def getParseVersionUrlFor(self, machine_id: int) -> str: + def getParseVersionUrlFor(self, machine_id: int) -> Optional[Callable]: return self._parse_version_url_per_machine.get(machine_id) - def getCheckUrlsFor(self, machine_id: int) -> [str]: + def getCheckUrlsFor(self, machine_id: int) -> Optional[List[str]]: return self._check_urls_per_machine.get(machine_id) - def getRedirectUserFor(self, machine_id: int) -> str: + def getRedirectUserFor(self, machine_id: int) -> Optional[str]: return self._redirect_user_per_machine.get(machine_id) From 69cef98c3041244bc9edb77ffe3f8c85f517ba19 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 12 Oct 2018 10:11:46 +0200 Subject: [PATCH 10/31] FirmwareUpdateChecker: Small fixes (typing and lowercase input). --- plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py | 5 ++--- plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index d186cbb4e4..09be95597b 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -39,9 +39,8 @@ class FirmwareUpdateCheckerJob(Job): try: request = urllib.request.Request(url, headers=self._headers) - current_version_file = urllib.request.urlopen(request) - reader = codecs.getreader("utf-8") - result = reader(current_version_file).read(firstline=True) + response = urllib.request.urlopen(request) + result = response.read().decode('utf-8') except URLError: Logger.log('w', "Could not reach '{0}', if this URL is old, consider removal.".format(url)) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py index f6d7a24da0..2e97a8869d 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py @@ -42,7 +42,7 @@ class FirmwareUpdateCheckerLookup: try: for machine_json in machines_json: machine_id = machine_json.get("id") - machine_name = machine_json.get("name") + machine_name = machine_json.get("name").lower() # Lower in case upper-case char are added to the json. self._machine_ids.append(machine_id) self._machine_per_name[machine_name] = machine_id version_parse_function = \ From f7bef851db0ab01d2bbd832ab3b466e17049661f Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 12 Oct 2018 11:09:46 +0200 Subject: [PATCH 11/31] Remove code duplication for recreate network timer --- cura/PrinterOutput/NetworkedPrinterOutputDevice.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py index d9c5707a03..f7c7f5d233 100644 --- a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py +++ b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py @@ -130,9 +130,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): # We need to check if the manager needs to be re-created. If we don't, we get some issues when OSX goes to # sleep. if time_since_last_response > self._recreate_network_manager_time: - if self._last_manager_create_time is None: - self._createNetworkManager() - elif time() - self._last_manager_create_time > self._recreate_network_manager_time: + if self._last_manager_create_time is None or time() - self._last_manager_create_time > self._recreate_network_manager_time: self._createNetworkManager() assert(self._manager is not None) elif self._connection_state == ConnectionState.closed: From ad80ea6dd47fdff1a67db98a66afa9e29202f031 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Fri, 12 Oct 2018 11:16:24 +0200 Subject: [PATCH 12/31] fix: limit to extruder of top/bottom polygon connector --- resources/definitions/fdmprinter.def.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 305d841175..9da27f5040 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -1221,7 +1221,7 @@ "type": "bool", "default_value": false, "enabled": "(top_layers > 0 or bottom_layers > 0) and top_bottom_pattern == 'concentric'", - "limit_to_extruder": "infill_extruder_nr", + "limit_to_extruder": "top_bottom_extruder_nr", "settable_per_mesh": true }, "skin_angles": From 3c626453a69c3c66c76540d8ca2035a396c58b74 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 12 Oct 2018 11:28:13 +0200 Subject: [PATCH 13/31] Fix spelling --- resources/definitions/fdmprinter.def.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 9da27f5040..f17dd63c0a 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -1217,7 +1217,7 @@ "connect_skin_polygons": { "label": "Connect Top/Bottom Polygons", - "description": "Connect top/bottom skin paths where they run next to each other. For the concentric pattern enabling this setting greatly reduces the travel time, but because the connections can happend midway over infill this feature can reduce the top surface quality.", + "description": "Connect top/bottom skin paths where they run next to each other. For the concentric pattern enabling this setting greatly reduces the travel time, but because the connections can happen midway over infill this feature can reduce the top surface quality.", "type": "bool", "default_value": false, "enabled": "(top_layers > 0 or bottom_layers > 0) and top_bottom_pattern == 'concentric'", From 85b835118dc91d829f7f5c31eb2b6b254f023d92 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 12 Oct 2018 13:24:09 +0200 Subject: [PATCH 14/31] Log which firmware file you're about to upload Kind of critical information, really. Contributes to issue CURA-5749. --- plugins/USBPrinting/USBPrinterOutputDevice.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index 36c5321180..769820d6d0 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -59,9 +59,9 @@ class USBPrinterOutputDevice(PrinterOutputDevice): self._all_baud_rates = [115200, 250000, 230400, 57600, 38400, 19200, 9600] # Instead of using a timer, we really need the update to be as a thread, as reading from serial can block. - self._update_thread = Thread(target=self._update, daemon = True) + self._update_thread = Thread(target = self._update, daemon = True) - self._update_firmware_thread = Thread(target=self._updateFirmware, daemon = True) + self._update_firmware_thread = Thread(target = self._updateFirmware, daemon = True) self._last_temperature_request = None # type: Optional[int] @@ -160,6 +160,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice): if self._connection_state != ConnectionState.closed: self.close() + Logger.log("i", "Uploading hex file from: {firmware_location}".format(firmware_location = self._firmware_location)) try: hex_file = intelHex.readHex(self._firmware_location) assert len(hex_file) > 0 From 287689a073befd0d4da7612d26810410b1a03ae0 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 12 Oct 2018 13:25:34 +0200 Subject: [PATCH 15/31] Don't cache the automatic firmware name The QML property was not updated when you change the printer. By not caching it, it gets the current printer's firmware file upon clicking the button. Simple and effective, and not that tough on computational power that it needs caching. Contributes to issue CURA-5749. --- .../UltimakerMachineActions/UpgradeFirmwareMachineAction.qml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml index ed771d2a04..fff7d2c46f 100644 --- a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml +++ b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Ultimaker B.V. +// Copyright (c) 2018 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.2 @@ -58,7 +58,6 @@ Cura.MachineAction anchors.horizontalCenter: parent.horizontalCenter width: childrenRect.width spacing: UM.Theme.getSize("default_margin").width - property var firmwareName: Cura.USBPrinterManager.getDefaultFirmwareName() Button { id: autoUpgradeButton @@ -66,7 +65,7 @@ Cura.MachineAction enabled: parent.firmwareName != "" && activeOutputDevice onClicked: { - activeOutputDevice.updateFirmware(parent.firmwareName) + activeOutputDevice.updateFirmware(Cura.USBPrinterManager.getDefaultFirmwareName()) } } Button From 99fc372b32058287e4113c211231458f1d129fae Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 12 Oct 2018 14:55:13 +0200 Subject: [PATCH 16/31] Update printer information when switching global container stacks This was just evaluated once during the creating of a USB connection. But you can switch out the printer without breaking/making a USB connection, so in that case we have to update it here. Contributes to issue CURA-5749. --- cura/PrinterOutput/PrinterOutputModel.py | 2 +- plugins/USBPrinting/USBPrinterOutputDevice.py | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/cura/PrinterOutput/PrinterOutputModel.py b/cura/PrinterOutput/PrinterOutputModel.py index f009a33178..cc9463baec 100644 --- a/cura/PrinterOutput/PrinterOutputModel.py +++ b/cura/PrinterOutput/PrinterOutputModel.py @@ -172,7 +172,7 @@ class PrinterOutputModel(QObject): def getController(self): return self._controller - @pyqtProperty(str, notify=nameChanged) + @pyqtProperty(str, notify = nameChanged) def name(self): return self._name diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index 769820d6d0..b61a62adc0 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -273,14 +273,18 @@ class USBPrinterOutputDevice(PrinterOutputDevice): except SerialException: Logger.log("w", "An exception occured while trying to create serial connection") return - container_stack = CuraApplication.getInstance().getGlobalContainerStack() - num_extruders = container_stack.getProperty("machine_extruder_count", "value") - # Ensure that a printer is created. - self._printers = [PrinterOutputModel(output_controller=GenericOutputController(self), number_of_extruders=num_extruders)] - self._printers[0].updateName(container_stack.getName()) + CuraApplication.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerStackChanged) + self._onGlobalContainerStackChanged() self.setConnectionState(ConnectionState.connected) self._update_thread.start() + def _onGlobalContainerStackChanged(self): + container_stack = CuraApplication.getInstance().getGlobalContainerStack() + num_extruders = container_stack.getProperty("machine_extruder_count", "value") + #Ensure that a printer is created. + self._printers = [PrinterOutputModel(output_controller = GenericOutputController(self), number_of_extruders = num_extruders)] + self._printers[0].updateName(container_stack.getName()) + def close(self): super().close() if self._serial is not None: From 9e4fcd820eaf67cf431c2e2fe9a3957d6c14d4f9 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 12 Oct 2018 14:56:27 +0200 Subject: [PATCH 17/31] Update outputDevice when the global container changed And directly link the active printer name to it, so that that also gets updated. With the property var it just gets evaluated upon creating the rectangle. Contributes to issue CURA-5749. --- resources/qml/PrintMonitor.qml | 2 +- resources/qml/PrinterOutput/OutputDeviceHeader.qml | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/resources/qml/PrintMonitor.qml b/resources/qml/PrintMonitor.qml index 3bfcea7025..12e95d1e89 100644 --- a/resources/qml/PrintMonitor.qml +++ b/resources/qml/PrintMonitor.qml @@ -44,7 +44,7 @@ Column Repeater { id: extrudersRepeater - model: activePrinter!=null ? activePrinter.extruders : null + model: activePrinter != null ? activePrinter.extruders : null ExtruderBox { diff --git a/resources/qml/PrinterOutput/OutputDeviceHeader.qml b/resources/qml/PrinterOutput/OutputDeviceHeader.qml index 03e6d78699..b5ed1b7b4e 100644 --- a/resources/qml/PrinterOutput/OutputDeviceHeader.qml +++ b/resources/qml/PrinterOutput/OutputDeviceHeader.qml @@ -14,11 +14,19 @@ Item implicitHeight: Math.floor(childrenRect.height + UM.Theme.getSize("default_margin").height * 2) property var outputDevice: null + Connections + { + target: Cura.MachineManager + onGlobalContainerChanged: + { + outputDevice = Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null; + } + } + Rectangle { height: childrenRect.height color: UM.Theme.getColor("setting_category") - property var activePrinter: outputDevice != null ? outputDevice.activePrinter : null Label { @@ -28,7 +36,7 @@ Item anchors.left: parent.left anchors.top: parent.top anchors.margins: UM.Theme.getSize("default_margin").width - text: outputDevice != null ? activePrinter.name : "" + text: outputDevice != null ? outputDevice.activePrinter.name : "" } Label From 6ac10db58248a3e0492b112ab816f39a9278a24f Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 12 Oct 2018 15:37:43 +0200 Subject: [PATCH 18/31] Code style: Use double quotes for strings Contributes to issue CURA-5483. --- .../FirmwareUpdateChecker.py | 8 ++++---- .../FirmwareUpdateCheckerJob.py | 12 ++++++------ .../FirmwareUpdateCheckerLookup.py | 16 ++++++++-------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py index 71bdd0bc23..e030d8f796 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. import os @@ -31,7 +31,7 @@ class FirmwareUpdateChecker(Extension): super().__init__() # Listen to a Signal that indicates a change in the list of printers, just if the user has enabled the - # 'check for updates' option + # "check for updates" option Application.getInstance().getPreferences().addPreference("info/automatic_update_check", True) if Application.getInstance().getPreferences().getValue("info/automatic_update_check"): ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded) @@ -49,9 +49,9 @@ class FirmwareUpdateChecker(Extension): if download_url is not None: QDesktopServices.openUrl(QUrl(download_url)) else: - Logger.log('e', "Can't find URL for {0}".format(action)) + Logger.log("e", "Can't find URL for {0}".format(action)) except Exception as ex: - Logger.log('e', "Don't know what to do with '{0}' because {1}".format(action, ex)) + Logger.log("e", "Don't know what to do with '{0}' because {1}".format(action, ex)) def _onContainerAdded(self, container): # Only take care when a new GlobalStack was added diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index 09be95597b..41cc2358c1 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -40,9 +40,9 @@ class FirmwareUpdateCheckerJob(Job): try: request = urllib.request.Request(url, headers=self._headers) response = urllib.request.urlopen(request) - result = response.read().decode('utf-8') + result = response.read().decode("utf-8") except URLError: - Logger.log('w', "Could not reach '{0}', if this URL is old, consider removal.".format(url)) + Logger.log("w", "Could not reach '{0}', if this URL is old, consider removal.".format(url)) return result @@ -58,7 +58,7 @@ class FirmwareUpdateCheckerJob(Job): max_version = version if max_version < self.EPSILON_VERSION: - Logger.log('w', "MachineID {0} not handled!".format(repr(machine_id))) + Logger.log("w", "MachineID {0} not handled!".format(repr(machine_id))) return max_version @@ -82,11 +82,11 @@ class FirmwareUpdateCheckerJob(Job): current_version = self.getCurrentVersionForMachine(machine_id) - # If it is the first time the version is checked, the checked_version is '' + # If it is the first time the version is checked, the checked_version is "" setting_key_str = get_settings_key_for_machine(machine_id) checked_version = Version(Application.getInstance().getPreferences().getValue(setting_key_str)) - # If the checked_version is '', it's because is the first time we check firmware and in this case + # If the checked_version is "", it's because is the first time we check firmware and in this case # we will not show the notification, but we will store it for the next time Application.getInstance().getPreferences().setValue(setting_key_str, current_version) Logger.log("i", "Reading firmware version of %s: checked = %s - latest = %s", machine_name, checked_version, current_version) @@ -115,7 +115,7 @@ class FirmwareUpdateCheckerJob(Job): message.actionTriggered.connect(self._callback) message.show() else: - Logger.log('i', "No machine with name {0} in list of firmware to check.".format(machine_name)) + Logger.log("i", "No machine with name {0} in list of firmware to check.".format(machine_name)) except Exception as e: Logger.log("w", "Failed to check for new version: %s", e) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py index 2e97a8869d..ec8e7cc073 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py @@ -17,23 +17,23 @@ def get_settings_key_for_machine(machine_id: int) -> str: def default_parse_version_response(response: str) -> Version: - raw_str = response.split('\n', 1)[0].rstrip() - return Version(raw_str.split('.')) # Split it into a list; the default parsing of 'single string' is different. + raw_str = response.split("\n", 1)[0].rstrip() + return Version(raw_str.split(".")) # Split it into a list; the default parsing of "single string" is different. class FirmwareUpdateCheckerLookup: JSON_NAME_TO_VERSION_PARSE_FUNCTION = {"default": default_parse_version_response} def __init__(self, json_path) -> None: - # Open the .json file with the needed lookup-lists for each machine(/model) and retrieve 'raw' json. + # Open the .json file with the needed lookup-lists for each machine(/model) and retrieve "raw" json. machines_json = None with open(json_path, "r", encoding="utf-8") as json_file: machines_json = json.load(json_file).get("machines") if machines_json is None: - Logger.log('e', "Missing or inaccessible: {0}".format(json_path)) + Logger.log("e", "Missing or inaccessible: {0}".format(json_path)) return - # Parse all the needed lookup-tables from the '.json' file(s) in the resources folder. + # Parse all the needed lookup-tables from the ".json" file(s) in the resources folder. self._machine_ids = [] # type:List[int] self._machine_per_name = {} # type:Dict[str, int] self._parse_version_url_per_machine = {} # type:Dict[int, Callable] @@ -48,15 +48,15 @@ class FirmwareUpdateCheckerLookup: version_parse_function = \ self.JSON_NAME_TO_VERSION_PARSE_FUNCTION.get(machine_json.get("version_parser")) if version_parse_function is None: - Logger.log('w', "No version-parse-function specified for machine {0}.".format(machine_name)) + Logger.log("w", "No version-parse-function specified for machine {0}.".format(machine_name)) version_parse_function = default_parse_version_response # Use default instead if nothing is found. self._parse_version_url_per_machine[machine_id] = version_parse_function - self._check_urls_per_machine[machine_id] = [] # Multiple check-urls: see '_comment' in the .json file. + self._check_urls_per_machine[machine_id] = [] # Multiple check-urls: see "_comment" in the .json file. for check_url in machine_json.get("check_urls"): self._check_urls_per_machine[machine_id].append(check_url) self._redirect_user_per_machine[machine_id] = machine_json.get("update_url") except Exception as ex: - Logger.log('e', "Couldn't parse firmware-update-check loopup-lists from file because {0}.".format(ex)) + Logger.log("e", "Couldn't parse firmware-update-check loopup-lists from file because {0}.".format(ex)) def getMachineIds(self) -> List[int]: return self._machine_ids From e3b05f086740b0faba00d13a21ab5d3a23a0c224 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 12 Oct 2018 16:46:39 +0200 Subject: [PATCH 19/31] Code style: Spaces around binary operators Also removed the unused machines_json value. Contributes to issue CURA-5483. --- .../FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py | 10 +++++----- .../FirmwareUpdateCheckerLookup.py | 3 +-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index 41cc2358c1..ee5eaac25b 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -38,7 +38,7 @@ class FirmwareUpdateCheckerJob(Job): result = self.STRING_ZERO_VERSION try: - request = urllib.request.Request(url, headers=self._headers) + request = urllib.request.Request(url, headers = self._headers) response = urllib.request.urlopen(request) result = response.read().decode("utf-8") except URLError: @@ -100,8 +100,8 @@ class FirmwareUpdateCheckerJob(Job): message = Message(i18n_catalog.i18nc( "@info Don't translate {machine_name}, since it gets replaced by a printer name!", "New features are available for your {machine_name}! It is recommended to update the firmware on your printer.").format( - machine_name=machine_name), - title=i18n_catalog.i18nc( + machine_name = machine_name), + title = i18n_catalog.i18nc( "@info:title The %s gets replaced with the printer name.", "New %s firmware available") % machine_name) @@ -109,8 +109,8 @@ class FirmwareUpdateCheckerJob(Job): i18n_catalog.i18nc("@action:button", "How to update"), "[no_icon]", "[no_description]", - button_style=Message.ActionButtonStyle.LINK, - button_align=Message.ActionButtonStyle.BUTTON_ALIGN_LEFT) + button_style = Message.ActionButtonStyle.LINK, + button_align = Message.ActionButtonStyle.BUTTON_ALIGN_LEFT) message.actionTriggered.connect(self._callback) message.show() diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py index ec8e7cc073..e283d58b2b 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py @@ -26,8 +26,7 @@ class FirmwareUpdateCheckerLookup: def __init__(self, json_path) -> None: # Open the .json file with the needed lookup-lists for each machine(/model) and retrieve "raw" json. - machines_json = None - with open(json_path, "r", encoding="utf-8") as json_file: + with open(json_path, "r", encoding = "utf-8") as json_file: machines_json = json.load(json_file).get("machines") if machines_json is None: Logger.log("e", "Missing or inaccessible: {0}".format(json_path)) From 1b7055f0f39f339f8bd1d4801203732ed8b1d318 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 12 Oct 2018 17:03:48 +0200 Subject: [PATCH 20/31] Fix spelling of error message Loopup -> Lookup. Contributes to issue CURA-5483. --- plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py index e283d58b2b..6d96ee36bb 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py @@ -55,7 +55,7 @@ class FirmwareUpdateCheckerLookup: self._check_urls_per_machine[machine_id].append(check_url) self._redirect_user_per_machine[machine_id] = machine_json.get("update_url") except Exception as ex: - Logger.log("e", "Couldn't parse firmware-update-check loopup-lists from file because {0}.".format(ex)) + Logger.log("e", "Couldn't parse firmware-update-check lookup-lists from file because {0}.".format(ex)) def getMachineIds(self) -> List[int]: return self._machine_ids From 6cf2e89f6b37b5d077d20b8e8dae2dd81c97c18c Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Sat, 13 Oct 2018 16:40:26 +0200 Subject: [PATCH 21/31] Document CameraImageProvider Makes it easier than looking up the Qt documentation online. --- cura/CameraImageProvider.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/cura/CameraImageProvider.py b/cura/CameraImageProvider.py index 6a07f6b029..edb0f205c7 100644 --- a/cura/CameraImageProvider.py +++ b/cura/CameraImageProvider.py @@ -1,15 +1,26 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + from PyQt5.QtGui import QImage from PyQt5.QtQuick import QQuickImageProvider from PyQt5.QtCore import QSize from UM.Application import Application - +## Creates screenshots of the current scene. class CameraImageProvider(QQuickImageProvider): def __init__(self): super().__init__(QQuickImageProvider.Image) ## Request a new image. + # + # The image will be taken using the current camera position. + # Only the actual objects in the scene will get rendered. Not the build + # plate and such! + # \param id The ID for the image to create. This is the requested image + # source, with the "image:" scheme and provider identifier removed. It's + # a Qt thing, they'll provide this parameter. + # \param size The dimensions of the image to scale to. def requestImage(self, id, size): for output_device in Application.getInstance().getOutputDeviceManager().getOutputDevices(): try: From 60408c14bcac5aac9ac8b623f1d455b06032cd09 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Sat, 13 Oct 2018 19:21:22 +0200 Subject: [PATCH 22/31] FirmwareUpdateChecker: Small refactors due to code review. --- .../FirmwareUpdateChecker.py | 33 ++++++++++--------- .../FirmwareUpdateCheckerJob.py | 6 ++-- .../FirmwareUpdateCheckerLookup.py | 10 +++--- .../resources/machines.json | 2 +- 4 files changed, 27 insertions(+), 24 deletions(-) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py index e030d8f796..61604ff78b 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py @@ -5,19 +5,20 @@ import os from PyQt5.QtCore import QUrl from PyQt5.QtGui import QDesktopServices -from typing import List +from typing import Set from UM.Extension import Extension from UM.Application import Application from UM.Logger import Logger from UM.PluginRegistry import PluginRegistry +from UM.Qt.QtApplication import QtApplication from UM.i18n import i18nCatalog from UM.Settings.ContainerRegistry import ContainerRegistry from cura.Settings.GlobalStack import GlobalStack from .FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob -from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup, get_settings_key_for_machine +from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup, getSettingsKeyForMachine i18n_catalog = i18nCatalog("cura") @@ -36,22 +37,23 @@ class FirmwareUpdateChecker(Extension): if Application.getInstance().getPreferences().getValue("info/automatic_update_check"): ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded) - self._late_init = True # Init some things after creation, since we need the path from the plugin-mgr. + # Partly initialize after creation, since we need our own path from the plugin-manager. self._download_url = None self._check_job = None - self._name_cache = [] # type: List[str] + self._checked_printer_names = [] # type: Set[str] self._lookups = None + QtApplication.pluginsLoaded.connect(self._onPluginsLoaded) ## Callback for the message that is spawned when there is a new version. def _onActionTriggered(self, message, action): - try: download_url = self._lookups.getRedirectUserFor(int(action)) if download_url is not None: - QDesktopServices.openUrl(QUrl(download_url)) + if QDesktopServices.openUrl(QUrl(download_url)): + Logger.log("i", "Redirected browser to {0} to show newly available firmware.".format(download_url)) + else: + Logger.log("e", "Can't reach URL: {0}".format(download_url)) else: Logger.log("e", "Can't find URL for {0}".format(action)) - except Exception as ex: - Logger.log("e", "Don't know what to do with '{0}' because {1}".format(action, ex)) def _onContainerAdded(self, container): # Only take care when a new GlobalStack was added @@ -61,8 +63,9 @@ class FirmwareUpdateChecker(Extension): def _onJobFinished(self, *args, **kwargs): self._check_job = None - def doLateInit(self): - self._late_init = False + def _onPluginsLoaded(self): + if self._lookups is not None: + return self._lookups = FirmwareUpdateCheckerLookup(os.path.join(PluginRegistry.getInstance().getPluginPath( "FirmwareUpdateChecker"), "resources/machines.json")) @@ -70,7 +73,7 @@ class FirmwareUpdateChecker(Extension): # Initialize the Preference called `latest_checked_firmware` that stores the last version # checked for each printer. for machine_id in self._lookups.getMachineIds(): - Application.getInstance().getPreferences().addPreference(get_settings_key_for_machine(machine_id), "") + Application.getInstance().getPreferences().addPreference(getSettingsKeyForMachine(machine_id), "") ## Connect with software.ultimaker.com, load latest.version and check version info. # If the version info is different from the current version, spawn a message to @@ -79,13 +82,13 @@ class FirmwareUpdateChecker(Extension): # \param silent type(boolean) Suppresses messages other than "new version found" messages. # This is used when checking for a new firmware version at startup. def checkFirmwareVersion(self, container = None, silent = False): - if self._late_init: - self.doLateInit() + if self._lookups is None: + self._onPluginsLoaded() container_name = container.definition.getName() - if container_name in self._name_cache: + if container_name in self._checked_printer_names: return - self._name_cache.append(container_name) + self._checked_printer_names.append(container_name) self._check_job = FirmwareUpdateCheckerJob(container = container, silent = silent, lookups = self._lookups, diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index ee5eaac25b..5bb9d076b6 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -12,7 +12,7 @@ from urllib.error import URLError from typing import Dict import codecs -from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup, get_settings_key_for_machine +from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup, getSettingsKeyForMachine from UM.i18n import i18nCatalog i18n_catalog = i18nCatalog("cura") @@ -78,12 +78,12 @@ class FirmwareUpdateCheckerJob(Job): # If it is not None, then we compare between the checked_version and the current_version machine_id = self._lookups.getMachineByName(machine_name.lower()) if machine_id is not None: - Logger.log("i", "You have a {0} in the printer list. Let's check the firmware!".format(machine_name)) + Logger.log("i", "You have a(n) {0} in the printer list. Let's check the firmware!".format(machine_name)) current_version = self.getCurrentVersionForMachine(machine_id) # If it is the first time the version is checked, the checked_version is "" - setting_key_str = get_settings_key_for_machine(machine_id) + setting_key_str = getSettingsKeyForMachine(machine_id) checked_version = Version(Application.getInstance().getPreferences().getValue(setting_key_str)) # If the checked_version is "", it's because is the first time we check firmware and in this case diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py index 6d96ee36bb..ceecef61ba 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py @@ -12,17 +12,17 @@ from UM.i18n import i18nCatalog i18n_catalog = i18nCatalog("cura") -def get_settings_key_for_machine(machine_id: int) -> str: +def getSettingsKeyForMachine(machine_id: int) -> str: return "info/latest_checked_firmware_for_{0}".format(machine_id) -def default_parse_version_response(response: str) -> Version: +def defaultParseVersionResponse(response: str) -> Version: raw_str = response.split("\n", 1)[0].rstrip() - return Version(raw_str.split(".")) # Split it into a list; the default parsing of "single string" is different. + return Version(raw_str) class FirmwareUpdateCheckerLookup: - JSON_NAME_TO_VERSION_PARSE_FUNCTION = {"default": default_parse_version_response} + JSON_NAME_TO_VERSION_PARSE_FUNCTION = {"default": defaultParseVersionResponse} def __init__(self, json_path) -> None: # Open the .json file with the needed lookup-lists for each machine(/model) and retrieve "raw" json. @@ -48,7 +48,7 @@ class FirmwareUpdateCheckerLookup: self.JSON_NAME_TO_VERSION_PARSE_FUNCTION.get(machine_json.get("version_parser")) if version_parse_function is None: Logger.log("w", "No version-parse-function specified for machine {0}.".format(machine_name)) - version_parse_function = default_parse_version_response # Use default instead if nothing is found. + version_parse_function = defaultParseVersionResponse # Use default instead if nothing is found. self._parse_version_url_per_machine[machine_id] = version_parse_function self._check_urls_per_machine[machine_id] = [] # Multiple check-urls: see "_comment" in the .json file. for check_url in machine_json.get("check_urls"): diff --git a/plugins/FirmwareUpdateChecker/resources/machines.json b/plugins/FirmwareUpdateChecker/resources/machines.json index 5dc9aadbbf..ee072f75c3 100644 --- a/plugins/FirmwareUpdateChecker/resources/machines.json +++ b/plugins/FirmwareUpdateChecker/resources/machines.json @@ -1,5 +1,5 @@ { - "_comment": "Multiple 'update_urls': The 'new'-style URL is the only way to check for S5 firmware, and in the future, the UM3 line will also switch over, but for now the old 'JEDI'-style URL is still needed.", + "_comment": "There are multiple 'check_urls', because sometimes an URL is about to be phased out, and it's useful to have a new 'future-proof' one at the ready.", "machines": [ From 8c71a8855c9f80ce0beb5daad6bd643633db010b Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Sat, 13 Oct 2018 19:36:11 +0200 Subject: [PATCH 23/31] FirmwareUpdateChecker: Remove superfluous 'version_parser' as a setting, since it broke lean principles. --- .../FirmwareUpdateCheckerJob.py | 9 ++++++--- .../FirmwareUpdateCheckerLookup.py | 12 ------------ .../FirmwareUpdateChecker/resources/machines.json | 9 +++------ 3 files changed, 9 insertions(+), 21 deletions(-) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index 5bb9d076b6..a873f17d61 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -46,14 +46,17 @@ class FirmwareUpdateCheckerJob(Job): return result + def parseVersionResponse(self, response: str) -> Version: + raw_str = response.split("\n", 1)[0].rstrip() + return Version(raw_str) + def getCurrentVersionForMachine(self, machine_id: int) -> Version: max_version = self.ZERO_VERSION machine_urls = self._lookups.getCheckUrlsFor(machine_id) - parse_function = self._lookups.getParseVersionUrlFor(machine_id) - if machine_urls is not None and parse_function is not None: + if machine_urls is not None: for url in machine_urls: - version = parse_function(self.getUrlResponse(url)) + version = self.parseVersionResponse(self.getUrlResponse(url)) if version > max_version: max_version = version diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py index ceecef61ba..4813e3ecbb 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py @@ -16,13 +16,7 @@ def getSettingsKeyForMachine(machine_id: int) -> str: return "info/latest_checked_firmware_for_{0}".format(machine_id) -def defaultParseVersionResponse(response: str) -> Version: - raw_str = response.split("\n", 1)[0].rstrip() - return Version(raw_str) - - class FirmwareUpdateCheckerLookup: - JSON_NAME_TO_VERSION_PARSE_FUNCTION = {"default": defaultParseVersionResponse} def __init__(self, json_path) -> None: # Open the .json file with the needed lookup-lists for each machine(/model) and retrieve "raw" json. @@ -44,12 +38,6 @@ class FirmwareUpdateCheckerLookup: machine_name = machine_json.get("name").lower() # Lower in case upper-case char are added to the json. self._machine_ids.append(machine_id) self._machine_per_name[machine_name] = machine_id - version_parse_function = \ - self.JSON_NAME_TO_VERSION_PARSE_FUNCTION.get(machine_json.get("version_parser")) - if version_parse_function is None: - Logger.log("w", "No version-parse-function specified for machine {0}.".format(machine_name)) - version_parse_function = defaultParseVersionResponse # Use default instead if nothing is found. - self._parse_version_url_per_machine[machine_id] = version_parse_function self._check_urls_per_machine[machine_id] = [] # Multiple check-urls: see "_comment" in the .json file. for check_url in machine_json.get("check_urls"): self._check_urls_per_machine[machine_id].append(check_url) diff --git a/plugins/FirmwareUpdateChecker/resources/machines.json b/plugins/FirmwareUpdateChecker/resources/machines.json index ee072f75c3..d9eaad0abf 100644 --- a/plugins/FirmwareUpdateChecker/resources/machines.json +++ b/plugins/FirmwareUpdateChecker/resources/machines.json @@ -11,8 +11,7 @@ "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources", "http://software.ultimaker.com/releases/firmware/9066/stable/version.txt" ], - "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware", - "version_parser": "default" + "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware" }, { "id": 9511, @@ -22,15 +21,13 @@ "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources", "http://software.ultimaker.com/releases/firmware/9511/stable/version.txt" ], - "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware", - "version_parser": "default" + "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware" }, { "id": 9051, "name": "ultimaker s5", "check_urls": ["http://software.ultimaker.com/releases/firmware/9051/stable/version.txt"], - "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware", - "version_parser": "default" + "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware" } ] } From 931143ceaabe02f88bd4f0eca7357ed494733c0c Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Sat, 13 Oct 2018 20:05:20 +0200 Subject: [PATCH 24/31] Added FirmwareUpdateCheckerMessage, so no variables have to be hidden in the action of a plain Message. --- .../FirmwareUpdateChecker.py | 11 ++++--- .../FirmwareUpdateCheckerJob.py | 19 ++---------- .../FirmwareUpdateCheckerLookup.py | 1 - .../FirmwareUpdateCheckerMessage.py | 31 +++++++++++++++++++ 4 files changed, 40 insertions(+), 22 deletions(-) create mode 100644 plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py index 61604ff78b..8c0ea1bea2 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py @@ -19,6 +19,7 @@ from cura.Settings.GlobalStack import GlobalStack from .FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup, getSettingsKeyForMachine +from .FirmwareUpdateCheckerMessage import FirmwareUpdateCheckerMessage i18n_catalog = i18nCatalog("cura") @@ -40,20 +41,22 @@ class FirmwareUpdateChecker(Extension): # Partly initialize after creation, since we need our own path from the plugin-manager. self._download_url = None self._check_job = None - self._checked_printer_names = [] # type: Set[str] + self._checked_printer_names = set() # type: Set[str] self._lookups = None QtApplication.pluginsLoaded.connect(self._onPluginsLoaded) ## Callback for the message that is spawned when there is a new version. def _onActionTriggered(self, message, action): - download_url = self._lookups.getRedirectUserFor(int(action)) + if action == FirmwareUpdateCheckerMessage.STR_ACTION_DOWNLOAD: + machine_id = message.getMachineId() + download_url = self._lookups.getRedirectUserFor(machine_id) if download_url is not None: if QDesktopServices.openUrl(QUrl(download_url)): Logger.log("i", "Redirected browser to {0} to show newly available firmware.".format(download_url)) else: Logger.log("e", "Can't reach URL: {0}".format(download_url)) else: - Logger.log("e", "Can't find URL for {0}".format(action)) + Logger.log("e", "Can't find URL for {0}".format(machine_id)) def _onContainerAdded(self, container): # Only take care when a new GlobalStack was added @@ -88,7 +91,7 @@ class FirmwareUpdateChecker(Extension): container_name = container.definition.getName() if container_name in self._checked_printer_names: return - self._checked_printer_names.append(container_name) + self._checked_printer_names.add(container_name) self._check_job = FirmwareUpdateCheckerJob(container = container, silent = silent, lookups = self._lookups, diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index a873f17d61..f39f4c8cea 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -10,9 +10,9 @@ from UM.Version import Version import urllib.request from urllib.error import URLError from typing import Dict -import codecs from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup, getSettingsKeyForMachine +from .FirmwareUpdateCheckerMessage import FirmwareUpdateCheckerMessage from UM.i18n import i18nCatalog i18n_catalog = i18nCatalog("cura") @@ -99,22 +99,7 @@ class FirmwareUpdateCheckerJob(Job): # notify the user when no new firmware version is available. if (checked_version != "") and (checked_version != current_version): Logger.log("i", "SHOWING FIRMWARE UPDATE MESSAGE") - - message = Message(i18n_catalog.i18nc( - "@info Don't translate {machine_name}, since it gets replaced by a printer name!", - "New features are available for your {machine_name}! It is recommended to update the firmware on your printer.").format( - machine_name = machine_name), - title = i18n_catalog.i18nc( - "@info:title The %s gets replaced with the printer name.", - "New %s firmware available") % machine_name) - - message.addAction(machine_id, - i18n_catalog.i18nc("@action:button", "How to update"), - "[no_icon]", - "[no_description]", - button_style = Message.ActionButtonStyle.LINK, - button_align = Message.ActionButtonStyle.BUTTON_ALIGN_LEFT) - + message = FirmwareUpdateCheckerMessage(machine_id, machine_name) message.actionTriggered.connect(self._callback) message.show() else: diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py index 4813e3ecbb..ff4e9ce73d 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py @@ -6,7 +6,6 @@ import json from typing import Callable, Dict, List, Optional from UM.Logger import Logger -from UM.Version import Version from UM.i18n import i18nCatalog i18n_catalog = i18nCatalog("cura") diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py new file mode 100644 index 0000000000..0f13796c29 --- /dev/null +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py @@ -0,0 +1,31 @@ + +from UM.i18n import i18nCatalog +from UM.Message import Message + +i18n_catalog = i18nCatalog("cura") + + +# Make a separate class, since we need an extra field: The machine-id that this message is about. +class FirmwareUpdateCheckerMessage(Message): + STR_ACTION_DOWNLOAD = "download" + + def __init__(self, machine_id: int, machine_name: str) -> None: + super().__init__(i18n_catalog.i18nc( + "@info Don't translate {machine_name}, since it gets replaced by a printer name!", + "New features are available for your {machine_name}! It is recommended to update the firmware on your printer.").format( + machine_name=machine_name), + title=i18n_catalog.i18nc( + "@info:title The %s gets replaced with the printer name.", + "New %s firmware available") % machine_name) + + self._machine_id = machine_id + + self.addAction(self.STR_ACTION_DOWNLOAD, + i18n_catalog.i18nc("@action:button", "How to update"), + "[no_icon]", + "[no_description]", + button_style=Message.ActionButtonStyle.LINK, + button_align=Message.ActionButtonStyle.BUTTON_ALIGN_LEFT) + + def getMachineId(self) -> int: + return self._machine_id From 2e3abbc9044c82e5dc858f52e34d027b0cbee10c Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Sat, 13 Oct 2018 21:55:33 +0200 Subject: [PATCH 25/31] Put the firmware-update meta-data in the 'normal' printer definitions and make the code handle that. --- .../FirmwareUpdateChecker.py | 31 +++------- .../FirmwareUpdateCheckerJob.py | 30 ++++++---- .../FirmwareUpdateCheckerLookup.py | 57 ++++++------------- .../FirmwareUpdateCheckerMessage.py | 6 +- .../resources/machines.json | 33 ----------- resources/definitions/ultimaker3.def.json | 11 +++- .../definitions/ultimaker3_extended.def.json | 11 +++- resources/definitions/ultimaker_s5.def.json | 7 ++- 8 files changed, 73 insertions(+), 113 deletions(-) delete mode 100644 plugins/FirmwareUpdateChecker/resources/machines.json diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py index 8c0ea1bea2..415931b7ec 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py @@ -10,15 +10,12 @@ from typing import Set from UM.Extension import Extension from UM.Application import Application from UM.Logger import Logger -from UM.PluginRegistry import PluginRegistry -from UM.Qt.QtApplication import QtApplication from UM.i18n import i18nCatalog from UM.Settings.ContainerRegistry import ContainerRegistry from cura.Settings.GlobalStack import GlobalStack from .FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob -from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup, getSettingsKeyForMachine from .FirmwareUpdateCheckerMessage import FirmwareUpdateCheckerMessage i18n_catalog = i18nCatalog("cura") @@ -38,18 +35,14 @@ class FirmwareUpdateChecker(Extension): if Application.getInstance().getPreferences().getValue("info/automatic_update_check"): ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded) - # Partly initialize after creation, since we need our own path from the plugin-manager. - self._download_url = None self._check_job = None self._checked_printer_names = set() # type: Set[str] - self._lookups = None - QtApplication.pluginsLoaded.connect(self._onPluginsLoaded) ## Callback for the message that is spawned when there is a new version. def _onActionTriggered(self, message, action): if action == FirmwareUpdateCheckerMessage.STR_ACTION_DOWNLOAD: machine_id = message.getMachineId() - download_url = self._lookups.getRedirectUserFor(machine_id) + download_url = message.getDownloadUrl() if download_url is not None: if QDesktopServices.openUrl(QUrl(download_url)): Logger.log("i", "Redirected browser to {0} to show newly available firmware.".format(download_url)) @@ -66,18 +59,6 @@ class FirmwareUpdateChecker(Extension): def _onJobFinished(self, *args, **kwargs): self._check_job = None - def _onPluginsLoaded(self): - if self._lookups is not None: - return - - self._lookups = FirmwareUpdateCheckerLookup(os.path.join(PluginRegistry.getInstance().getPluginPath( - "FirmwareUpdateChecker"), "resources/machines.json")) - - # Initialize the Preference called `latest_checked_firmware` that stores the last version - # checked for each printer. - for machine_id in self._lookups.getMachineIds(): - Application.getInstance().getPreferences().addPreference(getSettingsKeyForMachine(machine_id), "") - ## Connect with software.ultimaker.com, load latest.version and check version info. # If the version info is different from the current version, spawn a message to # allow the user to download it. @@ -85,16 +66,18 @@ class FirmwareUpdateChecker(Extension): # \param silent type(boolean) Suppresses messages other than "new version found" messages. # This is used when checking for a new firmware version at startup. def checkFirmwareVersion(self, container = None, silent = False): - if self._lookups is None: - self._onPluginsLoaded() - container_name = container.definition.getName() if container_name in self._checked_printer_names: return self._checked_printer_names.add(container_name) + metadata = container.definition.getMetaData().get("firmware_update_info") + if metadata is None: + Logger.log("i", "No machine with name {0} in list of firmware to check.".format(container_name)) + return + self._check_job = FirmwareUpdateCheckerJob(container = container, silent = silent, - lookups = self._lookups, + machine_name = container_name, metadata = metadata, callback = self._onActionTriggered) self._check_job.start() self._check_job.finished.connect(self._onJobFinished) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index f39f4c8cea..2e15208336 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -9,7 +9,7 @@ from UM.Version import Version import urllib.request from urllib.error import URLError -from typing import Dict +from typing import Dict, Optional from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup, getSettingsKeyForMachine from .FirmwareUpdateCheckerMessage import FirmwareUpdateCheckerMessage @@ -25,13 +25,15 @@ class FirmwareUpdateCheckerJob(Job): ZERO_VERSION = Version(STRING_ZERO_VERSION) EPSILON_VERSION = Version(STRING_EPSILON_VERSION) - def __init__(self, container, silent, lookups: FirmwareUpdateCheckerLookup, callback) -> None: + def __init__(self, container, silent, machine_name, metadata, callback) -> None: super().__init__() self._container = container self.silent = silent self._callback = callback - self._lookups = lookups + self._machine_name = machine_name + self._metadata = metadata + self._lookups = None # type:Optional[FirmwareUpdateCheckerLookup] self._headers = {} # type:Dict[str, str] # Don't set headers yet. def getUrlResponse(self, url: str) -> str: @@ -50,10 +52,12 @@ class FirmwareUpdateCheckerJob(Job): raw_str = response.split("\n", 1)[0].rstrip() return Version(raw_str) - def getCurrentVersionForMachine(self, machine_id: int) -> Version: + def getCurrentVersion(self) -> Version: max_version = self.ZERO_VERSION + if self._lookups is None: + return max_version - machine_urls = self._lookups.getCheckUrlsFor(machine_id) + machine_urls = self._lookups.getCheckUrls() if machine_urls is not None: for url in machine_urls: version = self.parseVersionResponse(self.getUrlResponse(url)) @@ -61,16 +65,20 @@ class FirmwareUpdateCheckerJob(Job): max_version = version if max_version < self.EPSILON_VERSION: - Logger.log("w", "MachineID {0} not handled!".format(repr(machine_id))) + Logger.log("w", "MachineID {0} not handled!".format(self._lookups.getMachineName())) return max_version def run(self): if self._lookups is None: - Logger.log("e", "Can not check for a new release. URL not set!") - return + self._lookups = FirmwareUpdateCheckerLookup(self._machine_name, self._metadata) try: + # Initialize a Preference that stores the last version checked for this printer. + Application.getInstance().getPreferences().addPreference( + getSettingsKeyForMachine(self._lookups.getMachineId()), "") + + # Get headers application_name = Application.getInstance().getApplicationName() application_version = Application.getInstance().getVersion() self._headers = {"User-Agent": "%s - %s" % (application_name, application_version)} @@ -79,11 +87,11 @@ class FirmwareUpdateCheckerJob(Job): machine_name = self._container.definition.getName() # If it is not None, then we compare between the checked_version and the current_version - machine_id = self._lookups.getMachineByName(machine_name.lower()) + machine_id = self._lookups.getMachineId() if machine_id is not None: Logger.log("i", "You have a(n) {0} in the printer list. Let's check the firmware!".format(machine_name)) - current_version = self.getCurrentVersionForMachine(machine_id) + current_version = self.getCurrentVersion() # If it is the first time the version is checked, the checked_version is "" setting_key_str = getSettingsKeyForMachine(machine_id) @@ -99,7 +107,7 @@ class FirmwareUpdateCheckerJob(Job): # notify the user when no new firmware version is available. if (checked_version != "") and (checked_version != current_version): Logger.log("i", "SHOWING FIRMWARE UPDATE MESSAGE") - message = FirmwareUpdateCheckerMessage(machine_id, machine_name) + message = FirmwareUpdateCheckerMessage(machine_id, machine_name, self._lookups.getRedirectUserUrl()) message.actionTriggered.connect(self._callback) message.show() else: diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py index ff4e9ce73d..a21ad3f0e5 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py @@ -1,11 +1,7 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -import json - -from typing import Callable, Dict, List, Optional - -from UM.Logger import Logger +from typing import List, Optional from UM.i18n import i18nCatalog i18n_catalog = i18nCatalog("cura") @@ -17,44 +13,23 @@ def getSettingsKeyForMachine(machine_id: int) -> str: class FirmwareUpdateCheckerLookup: - def __init__(self, json_path) -> None: - # Open the .json file with the needed lookup-lists for each machine(/model) and retrieve "raw" json. - with open(json_path, "r", encoding = "utf-8") as json_file: - machines_json = json.load(json_file).get("machines") - if machines_json is None: - Logger.log("e", "Missing or inaccessible: {0}".format(json_path)) - return - + def __init__(self, machine_name, machine_json) -> None: # Parse all the needed lookup-tables from the ".json" file(s) in the resources folder. - self._machine_ids = [] # type:List[int] - self._machine_per_name = {} # type:Dict[str, int] - self._parse_version_url_per_machine = {} # type:Dict[int, Callable] - self._check_urls_per_machine = {} # type:Dict[int, List[str]] - self._redirect_user_per_machine = {} # type:Dict[int, str] - try: - for machine_json in machines_json: - machine_id = machine_json.get("id") - machine_name = machine_json.get("name").lower() # Lower in case upper-case char are added to the json. - self._machine_ids.append(machine_id) - self._machine_per_name[machine_name] = machine_id - self._check_urls_per_machine[machine_id] = [] # Multiple check-urls: see "_comment" in the .json file. - for check_url in machine_json.get("check_urls"): - self._check_urls_per_machine[machine_id].append(check_url) - self._redirect_user_per_machine[machine_id] = machine_json.get("update_url") - except Exception as ex: - Logger.log("e", "Couldn't parse firmware-update-check lookup-lists from file because {0}.".format(ex)) + self._machine_id = machine_json.get("id") + self._machine_name = machine_name.lower() # Lower in-case upper-case chars are added to the original json. + self._check_urls = [] # type:List[str] + for check_url in machine_json.get("check_urls"): + self._check_urls.append(check_url) + self._redirect_user = machine_json.get("update_url") - def getMachineIds(self) -> List[int]: - return self._machine_ids + def getMachineId(self) -> Optional[int]: + return self._machine_id - def getMachineByName(self, machine_name: str) -> Optional[int]: - return self._machine_per_name.get(machine_name) + def getMachineName(self) -> Optional[int]: + return self._machine_name - def getParseVersionUrlFor(self, machine_id: int) -> Optional[Callable]: - return self._parse_version_url_per_machine.get(machine_id) + def getCheckUrls(self) -> Optional[List[str]]: + return self._check_urls - def getCheckUrlsFor(self, machine_id: int) -> Optional[List[str]]: - return self._check_urls_per_machine.get(machine_id) - - def getRedirectUserFor(self, machine_id: int) -> Optional[str]: - return self._redirect_user_per_machine.get(machine_id) + def getRedirectUserUrl(self) -> Optional[str]: + return self._redirect_user diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py index 0f13796c29..d509c432b4 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py @@ -9,7 +9,7 @@ i18n_catalog = i18nCatalog("cura") class FirmwareUpdateCheckerMessage(Message): STR_ACTION_DOWNLOAD = "download" - def __init__(self, machine_id: int, machine_name: str) -> None: + def __init__(self, machine_id: int, machine_name: str, download_url: str) -> None: super().__init__(i18n_catalog.i18nc( "@info Don't translate {machine_name}, since it gets replaced by a printer name!", "New features are available for your {machine_name}! It is recommended to update the firmware on your printer.").format( @@ -19,6 +19,7 @@ class FirmwareUpdateCheckerMessage(Message): "New %s firmware available") % machine_name) self._machine_id = machine_id + self._download_url = download_url self.addAction(self.STR_ACTION_DOWNLOAD, i18n_catalog.i18nc("@action:button", "How to update"), @@ -29,3 +30,6 @@ class FirmwareUpdateCheckerMessage(Message): def getMachineId(self) -> int: return self._machine_id + + def getDownloadUrl(self) -> str: + return self._download_url diff --git a/plugins/FirmwareUpdateChecker/resources/machines.json b/plugins/FirmwareUpdateChecker/resources/machines.json deleted file mode 100644 index d9eaad0abf..0000000000 --- a/plugins/FirmwareUpdateChecker/resources/machines.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "_comment": "There are multiple 'check_urls', because sometimes an URL is about to be phased out, and it's useful to have a new 'future-proof' one at the ready.", - - "machines": - [ - { - "id": 9066, - "name": "ultimaker 3", - "check_urls": - [ - "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources", - "http://software.ultimaker.com/releases/firmware/9066/stable/version.txt" - ], - "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware" - }, - { - "id": 9511, - "name": "ultimaker 3 extended", - "check_urls": - [ - "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources", - "http://software.ultimaker.com/releases/firmware/9511/stable/version.txt" - ], - "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware" - }, - { - "id": 9051, - "name": "ultimaker s5", - "check_urls": ["http://software.ultimaker.com/releases/firmware/9051/stable/version.txt"], - "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware" - } - ] -} diff --git a/resources/definitions/ultimaker3.def.json b/resources/definitions/ultimaker3.def.json index b1daa6b780..f5e31890f6 100644 --- a/resources/definitions/ultimaker3.def.json +++ b/resources/definitions/ultimaker3.def.json @@ -24,7 +24,16 @@ }, "first_start_actions": [ "DiscoverUM3Action" ], "supported_actions": [ "DiscoverUM3Action" ], - "supports_usb_connection": false + "supports_usb_connection": false, + "firmware_update_info": { + "id": 9066, + "check_urls": + [ + "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources", + "http://software.ultimaker.com/releases/firmware/9066/stable/version.txt" + ], + "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware" + } }, diff --git a/resources/definitions/ultimaker3_extended.def.json b/resources/definitions/ultimaker3_extended.def.json index eb3cda9320..d13857e428 100644 --- a/resources/definitions/ultimaker3_extended.def.json +++ b/resources/definitions/ultimaker3_extended.def.json @@ -23,7 +23,16 @@ "1": "ultimaker3_extended_extruder_right" }, "first_start_actions": [ "DiscoverUM3Action" ], - "supported_actions": [ "DiscoverUM3Action" ] + "supported_actions": [ "DiscoverUM3Action" ], + "firmware_update_info": { + "id": 9511, + "check_urls": + [ + "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources", + "http://software.ultimaker.com/releases/firmware/9511/stable/version.txt" + ], + "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware" + } }, "overrides": { diff --git a/resources/definitions/ultimaker_s5.def.json b/resources/definitions/ultimaker_s5.def.json index 2e634787af..6195933869 100644 --- a/resources/definitions/ultimaker_s5.def.json +++ b/resources/definitions/ultimaker_s5.def.json @@ -30,7 +30,12 @@ "first_start_actions": [ "DiscoverUM3Action" ], "supported_actions": [ "DiscoverUM3Action" ], "supports_usb_connection": false, - "weight": -1 + "weight": -1, + "firmware_update_info": { + "id": 9051, + "check_urls": ["http://software.ultimaker.com/releases/firmware/9051/stable/version.txt"], + "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware" + } }, "overrides": { From e747219bbec338736eb8ce683e15cf1cfbb77511 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 15 Oct 2018 01:30:36 +0200 Subject: [PATCH 26/31] Let Makerbot Replicator use Replicator's X3G variant --- resources/definitions/makerbotreplicator.def.json | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/definitions/makerbotreplicator.def.json b/resources/definitions/makerbotreplicator.def.json index 1770b7a979..3b02215e74 100644 --- a/resources/definitions/makerbotreplicator.def.json +++ b/resources/definitions/makerbotreplicator.def.json @@ -6,6 +6,7 @@ "visible": true, "author": "Ultimaker", "manufacturer": "MakerBot", + "machine_x3g_variant": "r1", "file_formats": "application/x3g", "platform_offset": [ 0, 0, 0], "machine_extruder_trains": From 73de7209477ea675475da5f6bd753fb6deeebba3 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Mon, 15 Oct 2018 09:27:53 +0200 Subject: [PATCH 27/31] Change material diameter to 1.75 for monoprice machines CURA-5817 --- .../extruders/monoprice_select_mini_v1_extruder_0.def.json | 2 +- .../extruders/monoprice_select_mini_v2_extruder_0.def.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/extruders/monoprice_select_mini_v1_extruder_0.def.json b/resources/extruders/monoprice_select_mini_v1_extruder_0.def.json index eef47c9b6f..e4a899d7af 100644 --- a/resources/extruders/monoprice_select_mini_v1_extruder_0.def.json +++ b/resources/extruders/monoprice_select_mini_v1_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/monoprice_select_mini_v2_extruder_0.def.json b/resources/extruders/monoprice_select_mini_v2_extruder_0.def.json index e0899304dd..b727cfce1f 100644 --- a/resources/extruders/monoprice_select_mini_v2_extruder_0.def.json +++ b/resources/extruders/monoprice_select_mini_v2_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } From 02efd7a1f5a312ea4124a45025ac2dc3bdac99da Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 15 Oct 2018 13:55:47 +0200 Subject: [PATCH 28/31] Correct some printers to use 1.75mm filament This should fix some underextrusion problems... Hmm. Sources: * https://alya3dp.com/pages/teknik-ozellikler * https://www.creality3d.cn/creality-cr-10-s4-3d-printer-p00098p1.html * https://www.creality3d.cn/creality-cr-10-s5-3d-printer-p00099p1.html * https://3dprint.com/3643/german-repraps-neo-3d-printer-now-available-in-the-us-uk/ * https://somosmaker.com/pegasus-impresora-3d/ * http://www.3dmaker.vn/3d-printer-3dmaker-starter/?lang=en# (assuming the filaments they sell on that website are compatible) * https://makezine.com/product-review/printrbot-play/ I could not find a source for the Deltabot, but got that information from here: https://github.com/Ultimaker/Cura/issues/4573 Contributes to issue CURA-5817. --- resources/extruders/alya3dp_extruder_0.def.json | 2 +- resources/extruders/creality_cr10s4_extruder_0.def.json | 2 +- resources/extruders/creality_cr10s5_extruder_0.def.json | 2 +- resources/extruders/deltabot_extruder_0.def.json | 2 +- resources/extruders/grr_neo_extruder_0.def.json | 2 +- resources/extruders/kupido_extruder_0.def.json | 2 +- resources/extruders/makeR_pegasus_extruder_0.def.json | 2 +- resources/extruders/maker_starter_extruder_0.def.json | 2 +- resources/extruders/printrbot_play_heated_extruder_0.def.json | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/resources/extruders/alya3dp_extruder_0.def.json b/resources/extruders/alya3dp_extruder_0.def.json index e34db5dfbf..3676f01ad2 100644 --- a/resources/extruders/alya3dp_extruder_0.def.json +++ b/resources/extruders/alya3dp_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/creality_cr10s4_extruder_0.def.json b/resources/extruders/creality_cr10s4_extruder_0.def.json index 9afe1cee35..8a40c6431f 100644 --- a/resources/extruders/creality_cr10s4_extruder_0.def.json +++ b/resources/extruders/creality_cr10s4_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/creality_cr10s5_extruder_0.def.json b/resources/extruders/creality_cr10s5_extruder_0.def.json index fed86eb2b5..98b701ae2e 100644 --- a/resources/extruders/creality_cr10s5_extruder_0.def.json +++ b/resources/extruders/creality_cr10s5_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/deltabot_extruder_0.def.json b/resources/extruders/deltabot_extruder_0.def.json index 43fce74fa5..e13d6a6ee3 100644 --- a/resources/extruders/deltabot_extruder_0.def.json +++ b/resources/extruders/deltabot_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.5 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/grr_neo_extruder_0.def.json b/resources/extruders/grr_neo_extruder_0.def.json index 9fe86d9eed..6d76c90796 100644 --- a/resources/extruders/grr_neo_extruder_0.def.json +++ b/resources/extruders/grr_neo_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.5 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/kupido_extruder_0.def.json b/resources/extruders/kupido_extruder_0.def.json index d93395e667..ef988d4fde 100644 --- a/resources/extruders/kupido_extruder_0.def.json +++ b/resources/extruders/kupido_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/makeR_pegasus_extruder_0.def.json b/resources/extruders/makeR_pegasus_extruder_0.def.json index 8d2a98340a..e37891abde 100644 --- a/resources/extruders/makeR_pegasus_extruder_0.def.json +++ b/resources/extruders/makeR_pegasus_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/maker_starter_extruder_0.def.json b/resources/extruders/maker_starter_extruder_0.def.json index 5c60e536b7..ee94250248 100644 --- a/resources/extruders/maker_starter_extruder_0.def.json +++ b/resources/extruders/maker_starter_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/printrbot_play_heated_extruder_0.def.json b/resources/extruders/printrbot_play_heated_extruder_0.def.json index ba8bc5c34c..0a3eeb3d06 100644 --- a/resources/extruders/printrbot_play_heated_extruder_0.def.json +++ b/resources/extruders/printrbot_play_heated_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } From d7a7cf3003f7ebe55d45b41c1ce20717b5c7d865 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 15 Oct 2018 13:55:47 +0200 Subject: [PATCH 29/31] Correct some printers to use 1.75mm filament This should fix some underextrusion problems... Hmm. Sources: * https://alya3dp.com/pages/teknik-ozellikler * https://www.creality3d.cn/creality-cr-10-s4-3d-printer-p00098p1.html * https://www.creality3d.cn/creality-cr-10-s5-3d-printer-p00099p1.html * https://3dprint.com/3643/german-repraps-neo-3d-printer-now-available-in-the-us-uk/ * https://somosmaker.com/pegasus-impresora-3d/ * http://www.3dmaker.vn/3d-printer-3dmaker-starter/?lang=en# (assuming the filaments they sell on that website are compatible) * https://makezine.com/product-review/printrbot-play/ I could not find a source for the Deltabot, but got that information from here: https://github.com/Ultimaker/Cura/issues/4573 Contributes to issue CURA-5817. --- resources/extruders/alya3dp_extruder_0.def.json | 2 +- resources/extruders/creality_cr10s4_extruder_0.def.json | 2 +- resources/extruders/creality_cr10s5_extruder_0.def.json | 2 +- resources/extruders/deltabot_extruder_0.def.json | 2 +- resources/extruders/grr_neo_extruder_0.def.json | 2 +- resources/extruders/kupido_extruder_0.def.json | 2 +- resources/extruders/makeR_pegasus_extruder_0.def.json | 2 +- resources/extruders/maker_starter_extruder_0.def.json | 2 +- resources/extruders/printrbot_play_heated_extruder_0.def.json | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/resources/extruders/alya3dp_extruder_0.def.json b/resources/extruders/alya3dp_extruder_0.def.json index e34db5dfbf..3676f01ad2 100644 --- a/resources/extruders/alya3dp_extruder_0.def.json +++ b/resources/extruders/alya3dp_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/creality_cr10s4_extruder_0.def.json b/resources/extruders/creality_cr10s4_extruder_0.def.json index 9afe1cee35..8a40c6431f 100644 --- a/resources/extruders/creality_cr10s4_extruder_0.def.json +++ b/resources/extruders/creality_cr10s4_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/creality_cr10s5_extruder_0.def.json b/resources/extruders/creality_cr10s5_extruder_0.def.json index fed86eb2b5..98b701ae2e 100644 --- a/resources/extruders/creality_cr10s5_extruder_0.def.json +++ b/resources/extruders/creality_cr10s5_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/deltabot_extruder_0.def.json b/resources/extruders/deltabot_extruder_0.def.json index 43fce74fa5..e13d6a6ee3 100644 --- a/resources/extruders/deltabot_extruder_0.def.json +++ b/resources/extruders/deltabot_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.5 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/grr_neo_extruder_0.def.json b/resources/extruders/grr_neo_extruder_0.def.json index 9fe86d9eed..6d76c90796 100644 --- a/resources/extruders/grr_neo_extruder_0.def.json +++ b/resources/extruders/grr_neo_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.5 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/kupido_extruder_0.def.json b/resources/extruders/kupido_extruder_0.def.json index d93395e667..ef988d4fde 100644 --- a/resources/extruders/kupido_extruder_0.def.json +++ b/resources/extruders/kupido_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/makeR_pegasus_extruder_0.def.json b/resources/extruders/makeR_pegasus_extruder_0.def.json index 8d2a98340a..e37891abde 100644 --- a/resources/extruders/makeR_pegasus_extruder_0.def.json +++ b/resources/extruders/makeR_pegasus_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/maker_starter_extruder_0.def.json b/resources/extruders/maker_starter_extruder_0.def.json index 5c60e536b7..ee94250248 100644 --- a/resources/extruders/maker_starter_extruder_0.def.json +++ b/resources/extruders/maker_starter_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } diff --git a/resources/extruders/printrbot_play_heated_extruder_0.def.json b/resources/extruders/printrbot_play_heated_extruder_0.def.json index ba8bc5c34c..0a3eeb3d06 100644 --- a/resources/extruders/printrbot_play_heated_extruder_0.def.json +++ b/resources/extruders/printrbot_play_heated_extruder_0.def.json @@ -11,6 +11,6 @@ "overrides": { "extruder_nr": { "default_value": 0 }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 2.85 } + "material_diameter": { "default_value": 1.75 } } } From 56a383814b78bc6dc9d7349381a4a4b23d286edc Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 15 Oct 2018 14:48:18 +0200 Subject: [PATCH 30/31] Code style: Spaces around binary operators Contributes to issue CURA-5483. --- .../FirmwareUpdateCheckerMessage.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py index d509c432b4..fd56c101a0 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py @@ -1,3 +1,5 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. from UM.i18n import i18nCatalog from UM.Message import Message @@ -13,8 +15,8 @@ class FirmwareUpdateCheckerMessage(Message): super().__init__(i18n_catalog.i18nc( "@info Don't translate {machine_name}, since it gets replaced by a printer name!", "New features are available for your {machine_name}! It is recommended to update the firmware on your printer.").format( - machine_name=machine_name), - title=i18n_catalog.i18nc( + machine_name = machine_name), + title = i18n_catalog.i18nc( "@info:title The %s gets replaced with the printer name.", "New %s firmware available") % machine_name) @@ -25,8 +27,8 @@ class FirmwareUpdateCheckerMessage(Message): i18n_catalog.i18nc("@action:button", "How to update"), "[no_icon]", "[no_description]", - button_style=Message.ActionButtonStyle.LINK, - button_align=Message.ActionButtonStyle.BUTTON_ALIGN_LEFT) + button_style = Message.ActionButtonStyle.LINK, + button_align = Message.ActionButtonStyle.BUTTON_ALIGN_LEFT) def getMachineId(self) -> int: return self._machine_id From 53dc28db891f1d49d4cd6662468fb8f68c272175 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 15 Oct 2018 15:12:42 +0200 Subject: [PATCH 31/31] Change URL of firmware update page for Ultimaker 3 and S5 I just got word of a new page to read up about the firmware update. Apparently we now have to link to this one. Contributes to issue CURA-5483. --- plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py | 2 +- resources/definitions/ultimaker3.def.json | 2 +- resources/definitions/ultimaker3_extended.def.json | 2 +- resources/definitions/ultimaker_s5.def.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index 2e15208336..4c60b95824 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from UM.Application import Application diff --git a/resources/definitions/ultimaker3.def.json b/resources/definitions/ultimaker3.def.json index f5e31890f6..72756de2a5 100644 --- a/resources/definitions/ultimaker3.def.json +++ b/resources/definitions/ultimaker3.def.json @@ -32,7 +32,7 @@ "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources", "http://software.ultimaker.com/releases/firmware/9066/stable/version.txt" ], - "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware" + "update_url": "https://ultimaker.com/firmware" } }, diff --git a/resources/definitions/ultimaker3_extended.def.json b/resources/definitions/ultimaker3_extended.def.json index d13857e428..68f26969b7 100644 --- a/resources/definitions/ultimaker3_extended.def.json +++ b/resources/definitions/ultimaker3_extended.def.json @@ -31,7 +31,7 @@ "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources", "http://software.ultimaker.com/releases/firmware/9511/stable/version.txt" ], - "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware" + "update_url": "https://ultimaker.com/firmware" } }, diff --git a/resources/definitions/ultimaker_s5.def.json b/resources/definitions/ultimaker_s5.def.json index 6195933869..310765dbc3 100644 --- a/resources/definitions/ultimaker_s5.def.json +++ b/resources/definitions/ultimaker_s5.def.json @@ -34,7 +34,7 @@ "firmware_update_info": { "id": 9051, "check_urls": ["http://software.ultimaker.com/releases/firmware/9051/stable/version.txt"], - "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware" + "update_url": "https://ultimaker.com/firmware" } },