diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index de57e5417a..322a9639cf 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -136,14 +136,14 @@ class CuraPackageManager(QObject): all_installed_ids = all_installed_ids.union(set(self._bundled_package_dict.keys())) if self._installed_package_dict.keys(): all_installed_ids = all_installed_ids.union(set(self._installed_package_dict.keys())) + all_installed_ids = all_installed_ids.difference(self._to_remove_package_set) + # If it's going to be installed and to be removed, then the package is being updated and it should be listed. if self._to_install_package_dict.keys(): all_installed_ids = all_installed_ids.union(set(self._to_install_package_dict.keys())) - all_installed_ids = all_installed_ids.difference(self._to_remove_package_set) # map of -> -> installed_packages_dict = {} for package_id in all_installed_ids: - # Skip required plugins as they should not be tampered with if package_id in Application.getInstance().getRequiredPlugins(): continue @@ -168,7 +168,7 @@ class CuraPackageManager(QObject): package_info["is_active"] = self._plugin_registry.isActivePlugin(package_id) # If the package ID is in bundled, label it as such - package_info["is_bundled"] = package_info["package_id"] in self._bundled_package_dict.keys() + package_info["is_bundled"] = package_info["package_id"] in self._bundled_package_dict.keys() and not self.isUserInstalledPackage(package_info["package_id"]) # If there is not a section in the dict for this type, add it if package_info["package_type"] not in installed_packages_dict: @@ -179,7 +179,7 @@ class CuraPackageManager(QObject): return installed_packages_dict - # Checks if the given package is installed. + # Checks if the given package is installed (at all). def isPackageInstalled(self, package_id: str) -> bool: return self.getInstalledPackageInfo(package_id) is not None @@ -194,11 +194,6 @@ class CuraPackageManager(QObject): return package_id = package_info["package_id"] - # Check the delayed installation and removal lists first - if package_id in self._to_remove_package_set: - self._to_remove_package_set.remove(package_id) - has_changes = True - # Check if it is installed installed_package_info = self.getInstalledPackageInfo(package_info["package_id"]) to_install_package = installed_package_info is None # Install if the package has not been installed @@ -235,23 +230,35 @@ class CuraPackageManager(QObject): self.installedPackagesChanged.emit() # Schedules the given package to be removed upon the next start. + # \param package_id id of the package + # \param force_add is used when updating. In that case you actually want to uninstall & install @pyqtSlot(str) - def removePackage(self, package_id: str) -> None: + def removePackage(self, package_id: str, force_add: bool = False) -> None: # Check the delayed installation and removal lists first if not self.isPackageInstalled(package_id): Logger.log("i", "Attempt to remove package [%s] that is not installed, do nothing.", package_id) return - # Remove from the delayed installation list if present - if package_id in self._to_install_package_dict: - del self._to_install_package_dict[package_id] + # Extra safety check + if package_id not in self._installed_package_dict and package_id in self._bundled_package_dict: + Logger.log("i", "Not uninstalling [%s] because it is a bundled package.") + return - # Schedule for a delayed removal: - self._to_remove_package_set.add(package_id) + if package_id not in self._to_install_package_dict or force_add: + # Schedule for a delayed removal: + self._to_remove_package_set.add(package_id) + else: + if package_id in self._to_install_package_dict: + # Remove from the delayed installation list if present + del self._to_install_package_dict[package_id] self._saveManagementData() self.installedPackagesChanged.emit() + ## Is the package an user installed package? + def isUserInstalledPackage(self, package_id: str): + return package_id in self._installed_package_dict + # Removes everything associated with the given package ID. def _purgePackage(self, package_id: str) -> None: # Iterate through all directories in the data storage directory and look for sub-directories that belong to diff --git a/plugins/Toolbox/resources/images/loading.gif b/plugins/Toolbox/resources/images/loading.gif new file mode 100644 index 0000000000..43cc1ed6d7 Binary files /dev/null and b/plugins/Toolbox/resources/images/loading.gif differ diff --git a/plugins/Toolbox/resources/images/loading.svg b/plugins/Toolbox/resources/images/loading.svg new file mode 100644 index 0000000000..1ceb4a8d7f --- /dev/null +++ b/plugins/Toolbox/resources/images/loading.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/Toolbox/resources/qml/ToolboxDetailTile.qml b/plugins/Toolbox/resources/qml/ToolboxDetailTile.qml index 80d50616e8..739dc4ccfe 100644 --- a/plugins/Toolbox/resources/qml/ToolboxDetailTile.qml +++ b/plugins/Toolbox/resources/qml/ToolboxDetailTile.qml @@ -20,7 +20,7 @@ Item { left: parent.left right: controls.left - rightMargin: UM.Theme.getSize("default_margin").width + rightMargin: UM.Theme.getSize("default_margin").width * 2 + UM.Theme.getSize("toolbox_loader").width top: parent.top } Label @@ -53,60 +53,25 @@ Item anchors.top: tile.top width: childrenRect.width height: childrenRect.height - Button + + ToolboxProgressButton { id: installButton - text: - { - if (installed) - { - return catalog.i18nc("@action:button", "Installed") - } - else - { - if (toolbox.isDownloading && toolbox.activePackage == model) - { - return catalog.i18nc("@action:button", "Cancel") - } - else - { - return catalog.i18nc("@action:button", "Install") - } - } + active: toolbox.isDownloading && toolbox.activePackage == model + complete: tile.installed + readyAction: function() { + toolbox.activePackage = model + toolbox.startDownload(model.download_url) } - enabled: installed || !(toolbox.isDownloading && toolbox.activePackage != model) //Don't allow installing while another download is running. + activeAction: function() { + toolbox.cancelDownload() + } + completeAction: function() { + toolbox.viewCategory = "installed" + } + // Don't allow installing while another download is running + enabled: installed || !(toolbox.isDownloading && toolbox.activePackage != model) opacity: enabled ? 1.0 : 0.5 - - property alias installed: tile.installed - style: UM.Theme.styles.toolbox_action_button - onClicked: - { - if (installed) - { - toolbox.viewCategory = "installed" - } - else - { - // if ( toolbox.isDownloading && toolbox.activePackage == model ) - if ( toolbox.isDownloading ) - { - toolbox.cancelDownload(); - } - else - { - toolbox.activePackage = model - // toolbox.activePackage = model; - if ( model.can_upgrade ) - { - // toolbox.downloadAndInstallPlugin( model.update_url ); - } - else - { - toolbox.startDownload( model.download_url ); - } - } - } - } } } diff --git a/plugins/Toolbox/resources/qml/ToolboxInstalledTile.qml b/plugins/Toolbox/resources/qml/ToolboxInstalledTile.qml index b585a084b3..435319b5e9 100644 --- a/plugins/Toolbox/resources/qml/ToolboxInstalledTile.qml +++ b/plugins/Toolbox/resources/qml/ToolboxInstalledTile.qml @@ -112,4 +112,4 @@ Item onMetadataChanged: canUpdate = toolbox.canUpdate(model.id) } } -} \ No newline at end of file +} diff --git a/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml b/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml index 1921bcb58e..5bbed2351c 100644 --- a/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml +++ b/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml @@ -11,56 +11,26 @@ Column width: UM.Theme.getSize("toolbox_action_button").width spacing: UM.Theme.getSize("narrow_margin").height - Item + ToolboxProgressButton { - width: parent.width - height: childrenRect.height + id: updateButton + active: toolbox.isDownloading && toolbox.activePackage == model + readyLabel: catalog.i18nc("@action:button", "Update") + activeLabel: catalog.i18nc("@action:button", "Updating") + completeLabel: catalog.i18nc("@action:button", "Updated") + readyAction: function() { + toolbox.activePackage = model + toolbox.update(model.id) + } + activeAction: function() { + toolbox.cancelDownload() + } + // Don't allow installing while another download is running + enabled: !(toolbox.isDownloading && toolbox.activePackage != model) + opacity: enabled ? 1.0 : 0.5 visible: canUpdate - Button - { - id: updateButton - text: catalog.i18nc("@action:button", "Update") - style: ButtonStyle - { - background: Rectangle - { - implicitWidth: UM.Theme.getSize("toolbox_action_button").width - implicitHeight: UM.Theme.getSize("toolbox_action_button").height - color: control.hovered ? UM.Theme.getColor("primary_hover") : UM.Theme.getColor("primary") - } - label: Label - { - text: control.text - color: control.hovered ? UM.Theme.getColor("button_text") : UM.Theme.getColor("button_text_hover") - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - font: UM.Theme.getFont("default_bold") - } - } - onClicked: toolbox.update(model.id) - } - ProgressBar - { - id: progressbar - width: parent.width - value: toolbox.isDownloading ? toolbox.downloadProgress : 0 - visible: toolbox.isDownloading - style: ProgressBarStyle - { - background: Rectangle - { - color: "transparent" - implicitHeight: UM.Theme.getSize("toolbox_action_button").height - } - progress: Rectangle - { - // TODO Define a good color that fits the purpuse - color: "blue" - opacity: 0.5 - } - } - } } + Button { id: removeButton @@ -90,4 +60,4 @@ Column } onClicked: toolbox.uninstall(model.id) } -} \ No newline at end of file +} diff --git a/plugins/Toolbox/resources/qml/ToolboxProgressButton.qml b/plugins/Toolbox/resources/qml/ToolboxProgressButton.qml new file mode 100644 index 0000000000..a977ef999b --- /dev/null +++ b/plugins/Toolbox/resources/qml/ToolboxProgressButton.qml @@ -0,0 +1,125 @@ +// Copyright (c) 2018 Ultimaker B.V. +// Toolbox is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.2 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import UM 1.1 as UM + + +Item +{ + id: base + + property var active: false + property var complete: false + + property var readyLabel: catalog.i18nc("@action:button", "Install") + property var activeLabel: catalog.i18nc("@action:button", "Cancel") + property var completeLabel: catalog.i18nc("@action:button", "Installed") + + property var readyAction: null // Action when button is ready and clicked (likely install) + property var activeAction: null // Action when button is active and clicked (likely cancel) + property var completeAction: null // Action when button is complete and clicked (likely go to installed) + + width: UM.Theme.getSize("toolbox_action_button").width + height: UM.Theme.getSize("toolbox_action_button").height + + Button + { + id: button + text: + { + if (complete) + { + return completeLabel + } + else if (active) + { + return activeLabel + } + else + { + return readyLabel + } + } + onClicked: + { + if (complete) + { + return completeAction() + } + else if (active) + { + return activeAction() + } + else + { + return readyAction() + } + } + style: ButtonStyle + { + background: Rectangle + { + implicitWidth: UM.Theme.getSize("toolbox_action_button").width + implicitHeight: UM.Theme.getSize("toolbox_action_button").height + color: + { + if (base.complete) + { + return UM.Theme.getColor("action_button_disabled") + } + else + { + if (control.hovered) + { + return UM.Theme.getColor("primary_hover") + } + else + { + return UM.Theme.getColor("primary") + } + } + } + } + label: Label + { + text: control.text + color: + { + if (base.complete) + { + return UM.Theme.getColor("action_button_disabled_text") + } + else + { + if (control.hovered) + { + return UM.Theme.getColor("button_text_hover") + } + else + { + return UM.Theme.getColor("button_text") + } + } + } + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + font: UM.Theme.getFont("default_bold") + } + } + } + + AnimatedImage + { + id: loader + visible: active + source: "../images/loading.gif" + width: UM.Theme.getSize("toolbox_loader").width + height: UM.Theme.getSize("toolbox_loader").height + anchors.right: button.left + anchors.rightMargin: UM.Theme.getSize("default_margin").width + anchors.verticalCenter: button.verticalCenter + } +} diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 2ba91dcdba..622198666d 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -61,6 +61,7 @@ class Toolbox(QObject, Extension): "plugins_showcase": QUrl("{base_url}/showcase".format(base_url = self._api_url)), "materials_showcase": QUrl("{base_url}/showcase".format(base_url = self._api_url)) } + self._to_update = [] # Package_ids that are waiting to be updated # Data: self._metadata = { @@ -216,13 +217,36 @@ class Toolbox(QObject, Extension): @pyqtSlot(str) def uninstall(self, plugin_id: str) -> None: - self._package_manager.removePackage(plugin_id) + self._package_manager.removePackage(plugin_id, force_add = True) self.installChanged.emit() self._updateInstalledModels() self.metadataChanged.emit() self._restart_required = True self.restartRequiredChanged.emit() + ## Actual update packages that are in self._to_update + def _update(self) -> None: + if self._to_update: + plugin_id = self._to_update.pop(0) + remote_package = self.getRemotePackage(plugin_id) + if remote_package: + download_url = remote_package["download_url"] + Logger.log("d", "Updating package [%s]..." % plugin_id) + if self._package_manager.isUserInstalledPackage(plugin_id): + self.uninstall(plugin_id) + self.startDownload(download_url) + else: + Logger.log("e", "Could not update package [%s] because there is no remote package info available.", plugin_id) + + if self._to_update: + self._application.callLater(self._update) + + ## Update a plugin by plugin_id + @pyqtSlot(str) + def update(self, plugin_id: str) -> None: + self._to_update.append(plugin_id) + self._application.callLater(self._update) + @pyqtSlot(str) def enable(self, plugin_id: str) -> None: self._plugin_registry.enablePlugin(plugin_id) @@ -251,6 +275,15 @@ class Toolbox(QObject, Extension): def restart(self): CuraApplication.getInstance().windowClosed() + def getRemotePackage(self, package_id: str) -> Optional[Dict]: + # TODO: make the lookup in a dict, not a loop. canUpdate is called for every item. + remote_package = None + for package in self._metadata["packages"]: + if package["package_id"] == package_id: + remote_package = package + break + return remote_package + # Checks # -------------------------------------------------------------------------- @pyqtSlot(str, result = bool) @@ -259,16 +292,13 @@ class Toolbox(QObject, Extension): if local_package is None: return False - remote_package = None - for package in self._metadata["packages"]: - if package["package_id"] == package_id: - remote_package = package + remote_package = self.getRemotePackage(package_id) if remote_package is None: return False - local_version = local_package["package_version"] - remote_version = remote_package["package_version"] - return Version(remote_version) > Version(local_version) + local_version = Version(local_package["package_version"]) + remote_version = Version(remote_package["package_version"]) + return remote_version > local_version @pyqtSlot(str, result = bool) def isInstalled(self, package_id: str) -> bool: @@ -321,8 +351,8 @@ class Toolbox(QObject, Extension): def resetDownload(self) -> None: if self._download_reply: - self._download_reply.abort() self._download_reply.downloadProgress.disconnect(self._onDownloadProgress) + self._download_reply.abort() self._download_reply = None self._download_request = None self.setDownloadProgress(0) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 7e726c5c60..5d7eed5c6d 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -3703,6 +3703,20 @@ "settable_per_mesh": false, "settable_per_extruder": true }, + "support_wall_count": + { + "label": "Support Wall Line Count", + "description": "The number of walls with which to surround support infill. Adding a wall can make support print more reliably and can support overhangs better, but increases print time and material used.", + "default_value": 1, + "minimum_value": "0", + "minimum_value_warning": "1 if support_pattern == 'concentric' else 0", + "maximum_value_warning": "3", + "type": "int", + "value": "1 if (support_pattern == 'grid' or support_pattern == 'triangles' or support_pattern == 'concentric') else 0", + "enabled": "support_enable", + "limit_to_extruder": "support_infill_extruder_nr", + "settable_per_mesh": true + }, "zig_zaggify_support": { "label": "Connect Support Lines", diff --git a/resources/setting_visibility/expert.cfg b/resources/setting_visibility/expert.cfg index d6989f8b26..db271cc985 100644 --- a/resources/setting_visibility/expert.cfg +++ b/resources/setting_visibility/expert.cfg @@ -220,6 +220,7 @@ support_bottom_extruder_nr support_type support_angle support_pattern +support_wall_count zig_zaggify_support support_connect_zigzags support_infill_rate diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index f2309fb4a9..4d75ecc1f1 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -458,6 +458,7 @@ "toolbox_header": [1.0, 4.0], "toolbox_progress_bar": [8.0, 0.5], "toolbox_chart_row": [1.0, 2.0], - "toolbox_action_button": [8.0, 2.5] + "toolbox_action_button": [8.0, 2.5], + "toolbox_loader": [2.0, 2.0] } }