diff --git a/cura/Machines/Models/IntentTranslations.py b/cura/Machines/Models/IntentTranslations.py index fd6a2db9ee..155039003c 100644 --- a/cura/Machines/Models/IntentTranslations.py +++ b/cura/Machines/Models/IntentTranslations.py @@ -1,8 +1,11 @@ import collections +from typing import Dict, Optional + from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") -intent_translations = collections.OrderedDict() # type: "collections.OrderedDict[str, Dict[str, Optional[str]]]" + +intent_translations = collections.OrderedDict() # type: collections.OrderedDict[str, Dict[str, Optional[str]]] intent_translations["default"] = { "name": catalog.i18nc("@label", "Default") } @@ -17,4 +20,4 @@ intent_translations["engineering"] = { intent_translations["quick"] = { "name": catalog.i18nc("@label", "Draft"), "description": catalog.i18nc("@text", "The draft profile is designed to print initial prototypes and concept validation with the intent of significant print time reduction.") -} \ No newline at end of file +} diff --git a/plugins/SimulationView/LayerSlider.qml b/plugins/SimulationView/LayerSlider.qml index 0c62dd01cf..6f608fe474 100644 --- a/plugins/SimulationView/LayerSlider.qml +++ b/plugins/SimulationView/LayerSlider.qml @@ -165,8 +165,9 @@ Item } onHeightChanged : { - // After a height change, the pixel-position of the lower handle is out of sync with the property value + // After a height change, the pixel-position of the handles is out of sync with the property value setLowerValue(lowerValue) + setUpperValue(upperValue) } // Upper handle diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index c8c809cc82..27197275b8 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -48,7 +48,7 @@ class Toolbox(QObject, Extension): self._download_progress = 0 # type: float self._is_downloading = False # type: bool self._network_manager = None # type: Optional[QNetworkAccessManager] - self._request_headers = [] # type: List[Tuple[bytes, bytes]] + self._request_headers = [] # type: List[Tuple[bytes, bytes]] self._updateRequestHeader() self._request_urls = {} # type: Dict[str, QUrl] @@ -59,13 +59,15 @@ class Toolbox(QObject, Extension): # The responses as given by the server parsed to a list. self._server_response_data = { "authors": [], - "packages": [] + "packages": [], + "updates": [], } # type: Dict[str, List[Any]] # Models: self._models = { "authors": AuthorsModel(self), "packages": PackagesModel(self), + "updates": PackagesModel(self), } # type: Dict[str, Union[AuthorsModel, PackagesModel]] self._plugins_showcase_model = PackagesModel(self) @@ -195,21 +197,27 @@ class Toolbox(QObject, Extension): cloud_api_version = self._cloud_api_version, sdk_version = self._sdk_version ) + + # We need to construct a query like installed_packages=ID:VERSION&installed_packages=ID:VERSION, etc. + installed_package_ids_with_versions = [":".join(items) for items in + self._package_manager.getAllInstalledPackageIdsAndVersions()] + installed_packages_query = "&installed_packages=".join(installed_package_ids_with_versions) + self._request_urls = { "authors": QUrl("{base_url}/authors".format(base_url = self._api_url)), - "packages": QUrl("{base_url}/packages".format(base_url = self._api_url)) + "packages": QUrl("{base_url}/packages".format(base_url = self._api_url)), + "updates": QUrl("{base_url}/packages/package-updates?installed_packages={query}".format( + base_url = self._api_url, query = installed_packages_query)) } self._application.getCuraAPI().account.loginStateChanged.connect(self._restart) - if CuraApplication.getInstance().getPreferences().getValue("info/automatic_update_check"): + # On boot we check which packages have updates. + if CuraApplication.getInstance().getPreferences().getValue("info/automatic_update_check") and len(installed_package_ids_with_versions) > 0: # Request the latest and greatest! - self._fetchPackageData() + self._fetchPackageUpdates() - def _fetchPackageData(self): - # Create the network manager: - # This was formerly its own function but really had no reason to be as - # it was never called more than once ever. + def _prepareNetworkManager(self): if self._network_manager is not None: self._network_manager.finished.disconnect(self._onRequestFinished) self._network_manager.networkAccessibleChanged.disconnect(self._onNetworkAccessibleChanged) @@ -217,10 +225,15 @@ class Toolbox(QObject, Extension): self._network_manager.finished.connect(self._onRequestFinished) self._network_manager.networkAccessibleChanged.connect(self._onNetworkAccessibleChanged) + def _fetchPackageUpdates(self): + self._prepareNetworkManager() + self._makeRequestByType("updates") + + def _fetchPackageData(self): + self._prepareNetworkManager() # Make remote requests: self._makeRequestByType("packages") self._makeRequestByType("authors") - # Gather installed packages: self._updateInstalledModels() @@ -248,7 +261,7 @@ class Toolbox(QObject, Extension): if not plugin_path: return None path = os.path.join(plugin_path, "resources", "qml", qml_name) - + dialog = self._application.createQmlComponent(path, {"toolbox": self}) if not dialog: raise Exception("Failed to create Marketplace dialog") @@ -632,7 +645,7 @@ class Toolbox(QObject, Extension): if not self._models[response_type]: Logger.log("e", "Could not find the %s model.", response_type) break - + self._server_response_data[response_type] = json_data["data"] self._models[response_type].setMetadata(self._server_response_data[response_type]) @@ -644,6 +657,10 @@ class Toolbox(QObject, Extension): elif response_type == "authors": self._models[response_type].setFilter({"package_types": "material"}) self._models[response_type].setFilter({"tags": "generic"}) + elif response_type == "updates": + # Tell the package manager that there's a new set of updates available. + packages = set([pkg["package_id"] for pkg in self._server_response_data[response_type]]) + self._package_manager.setPackagesWithUpdate(packages) self.metadataChanged.emit() @@ -674,7 +691,7 @@ class Toolbox(QObject, Extension): self.setIsDownloading(False) self._download_reply = cast(QNetworkReply, self._download_reply) self._download_reply.downloadProgress.disconnect(self._onDownloadProgress) - + # Check if the download was sucessfull if self._download_reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200: try: diff --git a/resources/definitions/leapfrog_bolt_pro.def.json b/resources/definitions/leapfrog_bolt_pro.def.json index 7f6e8ec2ec..09b9ecded5 100644 --- a/resources/definitions/leapfrog_bolt_pro.def.json +++ b/resources/definitions/leapfrog_bolt_pro.def.json @@ -1,117 +1,115 @@ - { - "version": 2, - "name": "Leapfrog Bolt Pro", - "inherits": "fdmprinter", - "metadata": { - "visible": true, - "author": "Karan and Vincent 20191104", - "manufacturer": "Leapfrog B.V.", - "category": "Other", - "platform": "leapfrog_bolt_pro_platform.stl", - "platform_offset": [0, 0, -14], - "file_formats": "text/x-gcode", - "supports_usb_connection": false, - "supports_network_connection": false, - "has_materials": true, - "has_machine_quality": true, - "has_variants": true, - "preferred_variant_name": "Brass 0.4", - "preferred_material": "leapfrog_epla_natural", - "variants_name": "Hot end", - "exclude_materials": [ - "generic_pla_175", - "generic_abs_175", - "generic_cpe_175", - "generic_hips_175", - "generic_nylon_175", - "generic_pc_175", - "generic_petg_175", - "generic_pva_175", - "generic_tpu_175", - "chromatik_pla", - "dsm_arnitel2045_175", - "dsm_novamid1070_175", - "emotiontech_abs", - "emotiontech_petg", - "emotiontech_pla", - "emotiontech_pva-m", - "emotiontech_pva-oks", - "emotiontech_pva-s", - "emotiontech_tpu98a", - "fabtotum_abs", - "fabtotum_nylon", - "fabtotum_pla", - "fabtotum_tpu", - "fiberlogy_hd_pla", - "filo3d_pla", - "filo3d_pla_green", - "filo3d_pla_red", - "imade3d_petg_175", - "imade3d_pla_175", - "innofill_innoflex60_175", - "octofiber_pla", - "polyflex_pla", - "polymax_pla", - "polyplus_pla", - "polywood_pla", - "tizyx_abs", - "tizyx_pla", - "tizyx_flex", - "tizyx_petg", - "tizyx_pva", - "tizyx_pla_bois", - "verbatim_bvoh_175", - "Vertex_Delta_ABS", - "Vertex_Delta_PET", - "Vertex_Delta_PLA_Glitter", - "Vertex_Delta_PLA_Mat", - "Vertex_Delta_PLA_Satin", - "Vertex_Delta_PLA_Wood", - "Vertex_Delta_PLA", - "Vertex_Delta_TPU", - "zyyx_pro_flex", - "zyyx_pro_pla" - ], - - "machine_extruder_trains": - { - "0": "leapfrog_bolt_pro_extruder_right", - "1": "leapfrog_bolt_pro_extruder_left" - } - }, - "overrides": { - "machine_name": {"default_value": "Leapfrog Bolt Pro" }, - "machine_extruder_count": {"default_value": 2}, - "machine_center_is_zero": {"default_value": false}, - "machine_width": {"default_value": 302}, - "machine_height": {"default_value": 205}, - "machine_depth": {"default_value": 322}, - "machine_heated_bed": {"default_value": true}, - "machine_head_with_fans_polygon": {"default_value": [[-60, 110 ], [-60, -45], [60, -45 ], [60, 110]]}, - "machine_max_feedrate_z": {"default_value": 16.7 }, - "machine_max_feedrate_e": {"default_value": 50 }, - "machine_max_acceleration_z": {"default_value": 100 }, - "machine_acceleration": {"default_value": 400 }, - "machine_max_jerk_xy": {"default_value": 20 }, - "machine_max_jerk_z": {"default_value": 0.4 }, - "machine_max_jerk_e": {"default_value": 5 }, - "machine_gcode_flavor": {"default_value": "RepRap (Marlin/Sprinter)"}, - "material_final_print_temperature": {"value": "default_material_print_temperature" }, - "material_initial_print_temperature": {"value": "default_material_print_temperature" }, - "gantry_height": {"value": "20"}, - "retraction_combing": { "default_value": "all" }, - "retraction_amount": {"default_value": 2}, - "adhesion_type": {"default_value": "skirt"}, - "skirt_line_count": {"default_value": 3}, - "machine_use_extruder_offset_to_offset_coords": {"default_value": true}, - "machine_start_gcode": {"default_value": "G90\nG28 X0 Y0 Z0\nG1 Z5 F1000\nG92 E0\nG1 Y-32 F12000\nG1 E15 F1000\nG1 E45 F150\nG4 S5"}, - "machine_end_gcode": {"default_value": "G92 E0\nG1 E-3 F300\nM104 S0 T0\nM104 S0 T1\nM140 S0\nG28 X0 Y0\nM84"}, - "prime_tower_enable": { "resolve": "extruders_enabled_count > 1"}, - "prime_tower_circular": {"default_value": false}, - "prime_tower_position_x": {"value": "169"}, - "prime_tower_position_y": {"value": "25"}, - "speed_travel": { "value": "200"}, - "build_volume_temperature": {"enabled": false}, - "material_standby_temperature": {"enabled": false } - } -} \ No newline at end of file +{ + "version": 2, + "name": "Leapfrog Bolt Pro", + "inherits": "fdmprinter", + "metadata": { + "visible": true, + "author": "Karan and Vincent 20191104", + "manufacturer": "Leapfrog B.V.", + "category": "Other", + "platform": "leapfrog_bolt_pro_platform.stl", + "platform_offset": [0, 0, -14], + "file_formats": "text/x-gcode", + "supports_usb_connection": false, + "supports_network_connection": false, + "has_materials": true, + "has_machine_quality": true, + "has_variants": true, + "preferred_variant_name": "Brass 0.4", + "preferred_material": "leapfrog_epla_natural", + "variants_name": "Hot end", + "exclude_materials": [ + "generic_pla_175", + "generic_abs_175", + "generic_cpe_175", + "generic_hips_175", + "generic_nylon_175", + "generic_pc_175", + "generic_petg_175", + "generic_pva_175", + "generic_tpu_175", + "chromatik_pla", + "dsm_arnitel2045_175", + "dsm_novamid1070_175", + "emotiontech_abs", + "emotiontech_petg", + "emotiontech_pla", + "emotiontech_pva-m", + "emotiontech_pva-oks", + "emotiontech_pva-s", + "emotiontech_tpu98a", + "fabtotum_abs", + "fabtotum_nylon", + "fabtotum_pla", + "fabtotum_tpu", + "fiberlogy_hd_pla", + "filo3d_pla", + "filo3d_pla_green", + "filo3d_pla_red", + "imade3d_petg_175", + "imade3d_pla_175", + "innofill_innoflex60_175", + "octofiber_pla", + "polyflex_pla", + "polymax_pla", + "polyplus_pla", + "polywood_pla", + "tizyx_abs", + "tizyx_pla", + "tizyx_flex", + "tizyx_petg", + "tizyx_pva", + "tizyx_pla_bois", + "verbatim_bvoh_175", + "Vertex_Delta_ABS", + "Vertex_Delta_PET", + "Vertex_Delta_PLA_Glitter", + "Vertex_Delta_PLA_Mat", + "Vertex_Delta_PLA_Satin", + "Vertex_Delta_PLA_Wood", + "Vertex_Delta_PLA", + "Vertex_Delta_TPU", + "zyyx_pro_flex", + "zyyx_pro_pla" + ], + "machine_extruder_trains": + { + "0": "leapfrog_bolt_pro_extruder_right", + "1": "leapfrog_bolt_pro_extruder_left" + } + }, + "overrides": { + "machine_name": {"default_value": "Leapfrog Bolt Pro" }, + "machine_extruder_count": {"default_value": 2}, + "machine_center_is_zero": {"default_value": false}, + "machine_width": {"default_value": 302}, + "machine_height": {"default_value": 205}, + "machine_depth": {"default_value": 322}, + "machine_heated_bed": {"default_value": true}, + "machine_head_with_fans_polygon": {"default_value": [[-60, 110 ], [-60, -45], [60, -45 ], [60, 110]]}, + "machine_max_feedrate_z": {"default_value": 16.7 }, + "machine_max_feedrate_e": {"default_value": 50 }, + "machine_max_acceleration_z": {"default_value": 100 }, + "machine_acceleration": {"default_value": 400 }, + "machine_max_jerk_xy": {"default_value": 20 }, + "machine_max_jerk_z": {"default_value": 0.4 }, + "machine_max_jerk_e": {"default_value": 5 }, + "machine_gcode_flavor": {"default_value": "RepRap (Marlin/Sprinter)"}, + "material_final_print_temperature": {"value": "default_material_print_temperature" }, + "material_initial_print_temperature": {"value": "default_material_print_temperature" }, + "gantry_height": {"value": "20"}, + "retraction_combing": { "default_value": "all" }, + "retraction_amount": {"default_value": 2}, + "adhesion_type": {"default_value": "skirt"}, + "skirt_line_count": {"default_value": 3}, + "machine_use_extruder_offset_to_offset_coords": {"default_value": true}, + "machine_start_gcode": {"default_value": "G90\nG28 X0 Y0 Z0\nG1 Z5 F1000\nG92 E0\nG1 Y-32 F12000\nG1 E15 F1000\nG1 E45 F150\nG4 S5"}, + "machine_end_gcode": {"default_value": "G92 E0\nG1 E-3 F300\nM104 S0 T0\nM104 S0 T1\nM140 S0\nG28 X0 Y0\nM84"}, + "prime_tower_enable": { "resolve": "extruders_enabled_count > 1"}, + "prime_tower_position_x": {"value": "169" }, + "prime_tower_position_y": {"value": "25" }, + "speed_travel": { "value": "200" }, + "build_volume_temperature": {"enabled": false}, + "material_standby_temperature": {"enabled": false } + } +} diff --git a/resources/extruders/leapfrog_bolt_pro_extruder_left.def.json b/resources/extruders/leapfrog_bolt_pro_extruder_left.def.json index f4ea1729fb..4ec859a60c 100644 --- a/resources/extruders/leapfrog_bolt_pro_extruder_left.def.json +++ b/resources/extruders/leapfrog_bolt_pro_extruder_left.def.json @@ -1,5 +1,4 @@ { - "id": "leapfrog_bolt_pro_extruder_left", "version": 2, "name": "Left extruder", "inherits": "fdmextruder", @@ -7,7 +6,6 @@ "machine": "leapfrog_bolt_pro", "position": "1" }, - "overrides": { "extruder_nr": { "default_value": 1, @@ -18,6 +16,6 @@ "machine_nozzle_head_distance": { "default_value": 22 }, "machine_nozzle_offset_x": { "default_value": 0 }, "machine_nozzle_offset_y": { "default_value": 0.0 }, - "machine_extruder_start_code": { "default_value": "G1 Y-32 F12000\nG1 X6 F1000\nG1 X-32 F4000\nG1 X6"} + "machine_extruder_start_code": { "default_value": "G1 Y-32 F12000\nG1 X6 F1000\nG1 X-32 F4000\nG1 X6" } } -} \ No newline at end of file +} diff --git a/resources/extruders/leapfrog_bolt_pro_extruder_right.def.json b/resources/extruders/leapfrog_bolt_pro_extruder_right.def.json index 2a6662ab2f..fe27106afd 100644 --- a/resources/extruders/leapfrog_bolt_pro_extruder_right.def.json +++ b/resources/extruders/leapfrog_bolt_pro_extruder_right.def.json @@ -1,5 +1,4 @@ { - "id": "leapfrog_bolt_pro_extruder_right", "version": 2, "name": "Right extruder", "inherits": "fdmextruder", @@ -7,17 +6,16 @@ "machine": "leapfrog_bolt_pro", "position": "0" }, - "overrides": { "extruder_nr": { "default_value": 0, "maximum_value": "1" }, "machine_nozzle_size": { "default_value": 0.4 }, - "material_diameter": { "default_value": 1.75 }, + "material_diameter": { "default_value": 1.75 }, "machine_nozzle_head_distance": { "default_value": 22 }, - "machine_nozzle_offset_x": { "default_value": 0}, + "machine_nozzle_offset_x": { "default_value": 0 }, "machine_nozzle_offset_y": { "default_value": 0.0 }, - "machine_extruder_start_code": { "default_value": "G1 Y-32 F12000\nG1 X296 F1000\nG1 X334 F4000\nG1 X296"} + "machine_extruder_start_code": { "default_value": "G1 Y-32 F12000\nG1 X296 F1000\nG1 X334 F4000\nG1 X296"} } -} \ No newline at end of file +} diff --git a/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml b/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml index 5e58faec78..7c527f9448 100644 --- a/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml +++ b/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml @@ -160,14 +160,20 @@ Item enabled: model.description !== undefined acceptedButtons: Qt.NoButton // react to hover only, don't steal clicks - onEntered: + Timer { - base.showTooltip( + id: intentTooltipTimer + interval: 500 + running: false + repeat: false + onTriggered: base.showTooltip( intentCategoryLabel, Qt.point(-(intentCategoryLabel.x - qualityRow.x) - UM.Theme.getSize("thick_margin").width, 0), model.description ) } + + onEntered: intentTooltipTimer.start() onExited: base.hideTooltip() } diff --git a/tests/Settings/TestDefinitionContainer.py b/tests/Settings/TestDefinitionContainer.py index 9e8e967692..38251e4397 100644 --- a/tests/Settings/TestDefinitionContainer.py +++ b/tests/Settings/TestDefinitionContainer.py @@ -3,11 +3,12 @@ import json # To check files for unnecessarily overridden properties. import os -import os.path import pytest #This module contains automated tests. from typing import Any, Dict import uuid +from unittest.mock import patch, MagicMock + import UM.Settings.ContainerRegistry #To create empty instance containers. import UM.Settings.ContainerStack #To set the container registry the container stacks use. from UM.Settings.DefinitionContainer import DefinitionContainer #To check against the class of DefinitionContainer. @@ -24,6 +25,10 @@ definition_filepaths = machine_filepaths + extruder_filepaths all_meshes = os.listdir(os.path.join(os.path.dirname(__file__), "..", "..", "resources", "meshes")) all_images = os.listdir(os.path.join(os.path.dirname(__file__), "..", "..", "resources", "images")) +# Loading definition files needs a functioning ContainerRegistry +cr = UM.Settings.ContainerRegistry.ContainerRegistry(None) + + @pytest.fixture def definition_container(): uid = str(uuid.uuid4()) @@ -39,7 +44,13 @@ def test_validateMachineDefinitionContainer(file_path, definition_container): if file_name == "fdmprinter.def.json" or file_name == "fdmextruder.def.json": return # Stop checking, these are root files. - assertIsDefinitionValid(definition_container, file_path) + from UM.VersionUpgradeManager import FilesDataUpdateResult + + mocked_vum = MagicMock() + mocked_vum.updateFilesData = lambda ct, v, fdl, fnl: FilesDataUpdateResult(ct, v, fdl, fnl) + with patch("UM.VersionUpgradeManager.VersionUpgradeManager.getInstance", MagicMock(return_value = mocked_vum)): + assertIsDefinitionValid(definition_container, file_path) + def assertIsDefinitionValid(definition_container, file_path): with open(file_path, encoding = "utf-8") as data: