Merge remote-tracking branch 'origin/master' into CURA-6959_collapse_categories

# Conflicts:
#	resources/qml/Menus/SettingVisibilityPresetsMenu.qml
#	resources/qml/Settings/SettingView.qml
This commit is contained in:
Nino van Hooff 2020-01-02 15:32:26 +01:00
commit 8dbd67f5bd
16 changed files with 313 additions and 80 deletions

View file

@ -77,6 +77,10 @@ class SettingVisibilityPresetsModel(QObject):
items.append(setting_visibility_preset)
# Add the "all" visibility:
all_setting_visibility_preset = SettingVisibilityPreset(preset_id = "all", name = "All", weight = 9001)
all_setting_visibility_preset.setSettings(list(CuraApplication.getInstance().getMachineManager().getAllSettingKeys()))
items.append(all_setting_visibility_preset)
# Sort them on weight (and if that fails, use ID)
items.sort(key = lambda k: (int(k.weight), k.presetId))

View file

@ -4,12 +4,11 @@
import time
import re
import unicodedata
from typing import Any, List, Dict, TYPE_CHECKING, Optional, cast
from typing import Any, List, Dict, TYPE_CHECKING, Optional, cast, Set
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, QTimer
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
from UM.Decorators import deprecated
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.Interfaces import ContainerInterface
@ -212,10 +211,13 @@ class MachineManager(QObject):
@pyqtProperty(int, constant=True)
def totalNumberOfSettings(self) -> int:
general_definition_containers = CuraContainerRegistry.getInstance().findDefinitionContainers(id = "fdmprinter")
return len(self.getAllSettingKeys())
def getAllSettingKeys(self) -> Set[str]:
general_definition_containers = CuraContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")
if not general_definition_containers:
return 0
return len(general_definition_containers[0].getAllKeys())
return set()
return general_definition_containers[0].getAllKeys()
## Triggered when the global container stack is changed in CuraApplication.
def _onGlobalContainerChanged(self) -> None:

View file

@ -292,8 +292,9 @@ class SimulationView(CuraView):
#
# \param layer_view_type integer as in SimulationView.qml and this class
def setSimulationViewType(self, layer_view_type: int) -> None:
self._layer_view_type = layer_view_type
self.currentLayerNumChanged.emit()
if layer_view_type != self._layer_view_type:
self._layer_view_type = layer_view_type
self.currentLayerNumChanged.emit()
## Return the layer view type, integer as in SimulationView.qml and this class
def getSimulationViewType(self) -> int:
@ -571,6 +572,8 @@ class SimulationView(CuraView):
def _onCurrentLayerNumChanged(self) -> None:
self.calculateMaxPathsOnLayer(self._current_layer_num)
scene = Application.getInstance().getController().getScene()
scene.sceneChanged.emit(scene.getRoot())
def _startUpdateTopLayers(self) -> None:
if not self._compatibility_mode:

View file

@ -149,6 +149,9 @@ class SimulationViewProxy(QObject):
self.currentPathChanged.emit()
self._layerActivityChanged()
scene = Application.getInstance().getController().getScene()
scene.sceneChanged.emit(scene.getRoot())
def _onMaxLayersChanged(self):
self.maxLayersChanged.emit()

View file

@ -0,0 +1,142 @@
// Copyright (c) 2020 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10
import QtQuick.Window 2.2
import QtQuick.Controls 2.3
import UM 1.1 as UM
import Cura 1.6 as Cura
UM.Dialog{
visible: true
title: catalog.i18nc("@title", "Changes from your account")
width: UM.Theme.getSize("popup_dialog").width
height: UM.Theme.getSize("popup_dialog").height
minimumWidth: width
maximumWidth: minimumWidth
minimumHeight: height
maximumHeight: minimumHeight
margin: 0
Rectangle
{
id: root
anchors.fill: parent
color: UM.Theme.getColor("main_background")
UM.I18nCatalog
{
id: catalog
name: "cura"
}
ScrollView
{
width: parent.width
height: parent.height - nextButton.height - nextButton.anchors.margins * 2 // We want some leftover space for the button at the bottom
clip: true
Column
{
anchors.fill: parent
anchors.margins: UM.Theme.getSize("default_margin").width
// Compatible packages
Label
{
font: UM.Theme.getFont("default")
text: catalog.i18nc("@label", "The following packages will be added:")
color: UM.Theme.getColor("text")
height: contentHeight + UM.Theme.getSize("default_margin").height
}
Repeater
{
model: toolbox.subscribedPackagesModel
Component
{
Item
{
width: parent.width
property var lineHeight: 60
visible: model.is_compatible == "True" ? true : false
height: visible ? (lineHeight + UM.Theme.getSize("default_margin").height) : 0 // We only show the compatible packages here
Image
{
id: packageIcon
source: model.icon_url || "../../images/logobot.svg"
height: lineHeight
width: height
mipmap: true
fillMode: Image.PreserveAspectFit
}
Label
{
text: model.name
font: UM.Theme.getFont("medium_bold")
anchors.left: packageIcon.right
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.verticalCenter: packageIcon.verticalCenter
color: UM.Theme.getColor("text")
elide: Text.ElideRight
}
}
}
}
// Incompatible packages
Label
{
font: UM.Theme.getFont("default")
text: catalog.i18nc("@label", "The following packages can not be installed because of incompatible Cura version:")
color: UM.Theme.getColor("text")
height: contentHeight + UM.Theme.getSize("default_margin").height
}
Repeater
{
model: toolbox.subscribedPackagesModel
Component
{
Item
{
width: parent.width
property var lineHeight: 60
visible: model.is_compatible == "True" ? false : true
height: visible ? (lineHeight + UM.Theme.getSize("default_margin").height) : 0 // We only show the incompatible packages here
Image
{
id: packageIcon
source: model.icon_url || "../../images/logobot.svg"
height: lineHeight
width: height
mipmap: true
fillMode: Image.PreserveAspectFit
}
Label
{
text: model.name
font: UM.Theme.getFont("medium_bold")
anchors.left: packageIcon.right
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.verticalCenter: packageIcon.verticalCenter
color: UM.Theme.getColor("text")
elide: Text.ElideRight
}
}
}
}
}
} // End of ScrollView
Cura.ActionButton
{
id: nextButton
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.margins: UM.Theme.getSize("default_margin").height
text: catalog.i18nc("@button", "Next")
}
}
}

View file

@ -0,0 +1,46 @@
# Copyright (c) 2020 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import Qt
from UM.Qt.ListModel import ListModel
from cura import ApplicationMetadata
class SubscribedPackagesModel(ListModel):
def __init__(self, parent = None):
super().__init__(parent)
self._metadata = None
self._discrepancies = None
self._sdk_version = ApplicationMetadata.CuraSDKVersion
self.addRoleName(Qt.UserRole + 1, "name")
self.addRoleName(Qt.UserRole + 2, "icon_url")
self.addRoleName(Qt.UserRole + 3, "is_compatible")
def setMetadata(self, data):
if self._metadata != data:
self._metadata = data
def addValue(self, discrepancy):
if self._discrepancies != discrepancy:
self._discrepancies = discrepancy
def update(self):
items = []
for item in self._metadata:
if item["package_id"] not in self._discrepancies:
continue
package = {"name": item["display_name"], "sdk_versions": item["sdk_versions"]}
if self._sdk_version not in item["sdk_versions"]:
package.update({"is_compatible": "False"})
else:
package.update({"is_compatible": "True"})
try:
package.update({"icon_url": item["icon_url"]})
except KeyError: # There is no 'icon_url" in the response payload for this package
package.update({"icon_url": ""})
items.append(package)
self.setItems(items)

View file

@ -15,6 +15,7 @@ from UM.PluginRegistry import PluginRegistry
from UM.Extension import Extension
from UM.i18n import i18nCatalog
from UM.Version import Version
from UM.Message import Message
from cura import ApplicationMetadata
from cura import UltimakerCloudAuthentication
@ -23,6 +24,7 @@ from cura.Machines.ContainerTree import ContainerTree
from .AuthorsModel import AuthorsModel
from .PackagesModel import PackagesModel
from .SubscribedPackagesModel import SubscribedPackagesModel
if TYPE_CHECKING:
from cura.Settings.GlobalStack import GlobalStack
@ -58,17 +60,19 @@ class Toolbox(QObject, Extension):
# The responses as given by the server parsed to a list.
self._server_response_data = {
"authors": [],
"packages": [],
"updates": [],
"authors": [],
"packages": [],
"updates": [],
"subscribed_packages": [],
} # type: Dict[str, List[Any]]
# Models:
self._models = {
"authors": AuthorsModel(self),
"packages": PackagesModel(self),
"updates": PackagesModel(self),
} # type: Dict[str, Union[AuthorsModel, PackagesModel]]
"authors": AuthorsModel(self),
"packages": PackagesModel(self),
"updates": PackagesModel(self),
"subscribed_packages": SubscribedPackagesModel(self),
} # type: Dict[str, Union[AuthorsModel, PackagesModel, SubscribedPackagesModel]]
self._plugins_showcase_model = PackagesModel(self)
self._plugins_available_model = PackagesModel(self)
@ -161,7 +165,7 @@ class Toolbox(QObject, Extension):
@pyqtSlot(str, int)
def ratePackage(self, package_id: str, rating: int) -> None:
url = QUrl("{base_url}/packages/{package_id}/ratings".format(base_url=self._api_url, package_id = package_id))
url = QUrl("{base_url}/packages/{package_id}/ratings".format(base_url = self._api_url, package_id = package_id))
self._rate_request = QNetworkRequest(url)
for header_name, header_value in self._request_headers:
@ -197,6 +201,11 @@ class Toolbox(QObject, Extension):
cloud_api_version = self._cloud_api_version,
sdk_version = self._sdk_version
)
# https://api.ultimaker.com/cura-packages/v1/user/packages
self._api_url_user_packages = "{cloud_api_root}/cura-packages/v{cloud_api_version}/user/packages".format(
cloud_api_root = self._cloud_api_root,
cloud_api_version = self._cloud_api_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
@ -207,15 +216,18 @@ class Toolbox(QObject, Extension):
"authors": QUrl("{base_url}/authors".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))
base_url = self._api_url, query = installed_packages_query)),
"subscribed_packages": QUrl(self._api_url_user_packages)
}
self._application.getCuraAPI().account.loginStateChanged.connect(self._restart)
self._application.getCuraAPI().account.loginStateChanged.connect(self._fetchUserSubscribedPackages)
# 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._fetchPackageUpdates()
self._fetchUserSubscribedPackages()
def _prepareNetworkManager(self):
if self._network_manager is not None:
@ -237,6 +249,11 @@ class Toolbox(QObject, Extension):
# Gather installed packages:
self._updateInstalledModels()
def _fetchUserSubscribedPackages(self):
if self._application.getCuraAPI().account.isLoggedIn:
self._prepareNetworkManager()
self._makeRequestByType("subscribed_packages")
# Displays the toolbox
@pyqtSlot()
def launch(self) -> None:
@ -540,9 +557,7 @@ class Toolbox(QObject, Extension):
@pyqtSlot(str, result = bool)
def isEnabled(self, package_id: str) -> bool:
if package_id in self._plugin_registry.getActivePlugins():
return True
return False
return package_id in self._plugin_registry.getActivePlugins()
# Check for plugins that were installed with the old plugin browser
def isOldPlugin(self, plugin_id: str) -> bool:
@ -561,10 +576,11 @@ class Toolbox(QObject, Extension):
# Make API Calls
# --------------------------------------------------------------------------
def _makeRequestByType(self, request_type: str) -> None:
Logger.log("d", "Requesting %s metadata from server.", request_type)
Logger.log("d", "Requesting '%s' metadata from server.", request_type)
request = QNetworkRequest(self._request_urls[request_type])
for header_name, header_value in self._request_headers:
request.setRawHeader(header_name, header_value)
self._updateRequestHeader()
if self._network_manager:
self._network_manager.get(request)
@ -661,6 +677,8 @@ class Toolbox(QObject, Extension):
# 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)
elif response_type == "subscribed_packages":
self._checkCompatibilities(json_data["data"])
self.metadataChanged.emit()
@ -674,9 +692,38 @@ class Toolbox(QObject, Extension):
Logger.log("w", "Unable to connect with the server, we got a response code %s while trying to connect to %s", reply.attribute(QNetworkRequest.HttpStatusCodeAttribute), reply.url())
self.setViewPage("errored")
self.resetDownload()
elif reply.operation() == QNetworkAccessManager.PutOperation:
# Ignore any operation that is not a get operation
pass
def _checkCompatibilities(self, json_data) -> None:
user_subscribed_packages = [plugin["package_id"] for plugin in json_data]
user_installed_packages = self._package_manager.getUserInstalledPackages()
# We check if there are packages installed in Cloud Marketplace but not in Cura marketplace (discrepancy)
package_discrepancy = list(set(user_subscribed_packages).difference(user_installed_packages))
if package_discrepancy:
self._models["subscribed_packages"].addValue(package_discrepancy)
self._models["subscribed_packages"].update()
Logger.log("d", "Discrepancy found between Cloud subscribed packages and Cura installed packages")
sync_message = Message(i18n_catalog.i18nc(
"@info:generic",
"\nDo you want to sync material and software packages with your account?"),
lifetime=0,
title=i18n_catalog.i18nc("@info:title", "Changes detected from your Ultimaker account", ))
sync_message.addAction("sync",
name=i18n_catalog.i18nc("@action:button", "Sync"),
icon="",
description="Sync your Cloud subscribed packages to your local environment.",
button_align=Message.ActionButtonAlignment.ALIGN_RIGHT)
sync_message.actionTriggered.connect(self._onSyncButtonClicked)
sync_message.show()
def _onSyncButtonClicked(self, sync_message: Message, sync_message_action: str) -> None:
sync_message.hide()
compatibility_dialog_path = "resources/qml/dialogs/CompatibilityDialog.qml"
plugin_path_prefix = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
if plugin_path_prefix:
path = os.path.join(plugin_path_prefix, compatibility_dialog_path)
self.compatibility_dialog_view = self._application.getInstance().createQmlComponent(path, {"toolbox": self})
# This function goes through all known remote versions of a package and notifies the package manager of this change
def _notifyPackageManager(self):
@ -772,39 +819,43 @@ class Toolbox(QObject, Extension):
# Exposed Models:
# --------------------------------------------------------------------------
@pyqtProperty(QObject, constant=True)
@pyqtProperty(QObject, constant = True)
def authorsModel(self) -> AuthorsModel:
return cast(AuthorsModel, self._models["authors"])
@pyqtProperty(QObject, constant=True)
@pyqtProperty(QObject, constant = True)
def subscribedPackagesModel(self) -> SubscribedPackagesModel:
return cast(SubscribedPackagesModel, self._models["subscribed_packages"])
@pyqtProperty(QObject, constant = True)
def packagesModel(self) -> PackagesModel:
return cast(PackagesModel, self._models["packages"])
@pyqtProperty(QObject, constant=True)
@pyqtProperty(QObject, constant = True)
def pluginsShowcaseModel(self) -> PackagesModel:
return self._plugins_showcase_model
@pyqtProperty(QObject, constant=True)
@pyqtProperty(QObject, constant = True)
def pluginsAvailableModel(self) -> PackagesModel:
return self._plugins_available_model
@pyqtProperty(QObject, constant=True)
@pyqtProperty(QObject, constant = True)
def pluginsInstalledModel(self) -> PackagesModel:
return self._plugins_installed_model
@pyqtProperty(QObject, constant=True)
@pyqtProperty(QObject, constant = True)
def materialsShowcaseModel(self) -> AuthorsModel:
return self._materials_showcase_model
@pyqtProperty(QObject, constant=True)
@pyqtProperty(QObject, constant = True)
def materialsAvailableModel(self) -> AuthorsModel:
return self._materials_available_model
@pyqtProperty(QObject, constant=True)
@pyqtProperty(QObject, constant = True)
def materialsInstalledModel(self) -> PackagesModel:
return self._materials_installed_model
@pyqtProperty(QObject, constant=True)
@pyqtProperty(QObject, constant = True)
def materialsGenericModel(self) -> PackagesModel:
return self._materials_generic_model

View file

@ -7650,7 +7650,7 @@
"default_value": 50,
"minimum_value": "1",
"minimum_value_warning": "25",
"maximum_value": "100",
"maximum_value_warning": "100",
"settable_per_mesh": true
},
"small_feature_speed_factor_0":
@ -7663,7 +7663,7 @@
"value": "small_feature_speed_factor",
"minimum_value": "1",
"minimum_value_warning": "25",
"maximum_value": "100",
"maximum_value_warning": "100",
"settable_per_mesh": true
}
}

View file

@ -14,7 +14,6 @@ Menu
property QtObject settingVisibilityPresetsModel: CuraApplication.getSettingVisibilityPresetsModel()
signal showAllSettings()
signal collapseAllCategories()
Instantiator
@ -37,17 +36,6 @@ Menu
onObjectRemoved: menu.removeItem(object)
}
MenuSeparator {}
MenuItem
{
text: catalog.i18nc("@action:inmenu", "Show All Settings")
checkable: false
exclusiveGroup: group
onTriggered:
{
showAllSettings();
}
}
MenuSeparator {}
MenuItem
{

View file

@ -19,7 +19,7 @@ Button
background: Rectangle
{
id: backgroundRectangle
implicitHeight: UM.Theme.getSize("section").height
height: UM.Theme.getSize("section").height
color:
{
if (base.color)

View file

@ -187,12 +187,6 @@ Item
menu: SettingVisibilityPresetsMenu
{
onShowAllSettings:
{
definitionsModel.setAllVisible(true)
filter.updateDefinitionModel()
}
onCollapseAllCategories:
{
settingsSearchTimer.stop()
@ -230,7 +224,6 @@ Item
ListView
{
id: contents
spacing: UM.Theme.getSize("default_lining").height
cacheBuffer: 1000000 // Set a large cache to effectively just cache every list item.
model: UM.SettingDefinitionsModel
@ -259,7 +252,7 @@ Item
id: delegate
width: scrollView.width
height: provider.properties.enabled === "True" ? UM.Theme.getSize("section").height : - contents.spacing
height: provider.properties.enabled === "True" ? UM.Theme.getSize("section").height + 2 * UM.Theme.getSize("default_lining").height : 0
Behavior on height { NumberAnimation { duration: 100 } }
opacity: provider.properties.enabled === "True" ? 1 : 0
Behavior on opacity { NumberAnimation { duration: 100 } }

View file

@ -12,7 +12,6 @@ material = generic_petg
[values]
material_print_temperature = =default_material_print_temperature + 35
material_bed_temperature = 70
cool_fan_enabled = False
speed_print = 30

View file

@ -12,7 +12,6 @@ material = generic_petg
[values]
material_print_temperature = =default_material_print_temperature + 35
material_bed_temperature = 70
cool_fan_enabled = False
speed_print = 30

View file

@ -12,7 +12,6 @@ material = generic_petg
[values]
material_print_temperature = =default_material_print_temperature + 35
material_bed_temperature = 70
cool_fan_enabled = False
speed_print = 30

View file

@ -1,8 +1,9 @@
#!/usr/bin/env python
import os
import sys
import subprocess
from multiprocessing.dummy import Pool
from functools import partial
from subprocess import call
# A quick Python implementation of unix 'where' command.
def where(exe_name: str, search_path: str = os.getenv("PATH")) -> str:
@ -62,21 +63,23 @@ def main():
mods = ["cura"] + plugins + findModules("plugins/VersionUpgrade")
success_code = 0
for mod in mods:
print("------------- Checking module {mod}".format(**locals()))
if sys.platform == "win32":
result = subprocess.run([mypy_module, "-p", mod, "--ignore-missing-imports"])
else:
result = subprocess.run([sys.executable, mypy_module, "-p", mod, "--ignore-missing-imports"])
if result.returncode != 0:
print("\nModule {mod} failed checking. :(".format(**locals()))
success_code = 1
if success_code:
print("\n\nSome modules failed checking!")
pool = Pool(2) # Run two commands at once
if sys.platform == "win32":
commands = ["%s -p %s --ignore-missing-imports" % (mypy_module, mod) for mod in mods]
else:
print("\n\nDone checking. All is good.")
commands = ["%s %s -p %s --ignore-missing-imports" % (sys.executable, mypy_module, mod) for mod in mods]
for i, returncode in enumerate(pool.imap(partial(call, shell=True), commands)):
if returncode != 0:
print("\nCommand %s failed checking. :(" % commands[i])
success_code = 1
if success_code:
print("MYPY check was compleded, but did not pass")
else:
print("MYPY check was compleded and passed with flying colors")
return success_code
if __name__ == "__main__":
sys.exit(main())
sys.exit(main())

View file

@ -1,4 +1,4 @@
from unittest.mock import MagicMock
from unittest.mock import MagicMock, patch
import os.path
@ -28,8 +28,8 @@ def test_createVisibilityPresetFromLocalFile():
def test_visibilityFromPrevious():
# This test checks that all settings in basic are in advanced and all settings in advanced are in expert.
visibility_model = SettingVisibilityPresetsModel(Preferences())
with patch("cura.CuraApplication.CuraApplication.getInstance"):
visibility_model = SettingVisibilityPresetsModel(Preferences())
basic_visibility = visibility_model.getVisibilityPresetById("basic")
advanced_visibility = visibility_model.getVisibilityPresetById("advanced")
@ -46,7 +46,8 @@ def test_visibilityFromPrevious():
def test_setActivePreset():
preferences = Preferences()
visibility_model = SettingVisibilityPresetsModel(preferences)
with patch("cura.CuraApplication.CuraApplication.getInstance"):
visibility_model = SettingVisibilityPresetsModel(preferences)
visibility_model.activePresetChanged = MagicMock()
# Ensure that we start off with basic (since we didn't change anyting just yet!)
assert visibility_model.activePreset == "basic"
@ -71,13 +72,13 @@ def test_preferenceChanged():
preferences = Preferences()
# Set the visible_settings to something silly
preferences.addPreference("general/visible_settings", "omgzomg")
visibility_model = SettingVisibilityPresetsModel(preferences)
with patch("cura.CuraApplication.CuraApplication.getInstance"):
visibility_model = SettingVisibilityPresetsModel(preferences)
visibility_model.activePresetChanged = MagicMock()
assert visibility_model.activePreset == "custom" # This should make the model start at "custom
assert visibility_model.activePresetChanged.emit.call_count == 0
basic_visibility = visibility_model.getVisibilityPresetById("basic")
new_visibility_string = ";".join(basic_visibility.settings)
preferences.setValue("general/visible_settings", new_visibility_string)