Merge branch '4.0' into CURA-5876_fixes

This commit is contained in:
Diego Prado Gesto 2018-12-12 17:06:59 +01:00
commit f38f140430
40 changed files with 463 additions and 477 deletions

View File

@ -83,7 +83,14 @@ class BuildVolume(SceneNode):
" with printed models."), title = catalog.i18nc("@info:title", "Build Volume"))
self._global_container_stack = None
self._stack_change_timer = QTimer()
self._stack_change_timer.setInterval(100)
self._stack_change_timer.setSingleShot(True)
self._stack_change_timer.timeout.connect(self._onStackChangeTimerFinished)
self._application.globalContainerStackChanged.connect(self._onStackChanged)
self._onStackChanged()
self._engine_ready = False
@ -105,6 +112,8 @@ class BuildVolume(SceneNode):
self._setting_change_timer.setSingleShot(True)
self._setting_change_timer.timeout.connect(self._onSettingChangeTimerFinished)
# Must be after setting _build_volume_message, apparently that is used in getMachineManager.
# activeQualityChanged is always emitted after setActiveVariant, setActiveMaterial and setActiveQuality.
# Therefore this works.
@ -526,8 +535,11 @@ class BuildVolume(SceneNode):
if extra_z != self._extra_z_clearance:
self._extra_z_clearance = extra_z
## Update the build volume visualization
def _onStackChanged(self):
self._stack_change_timer.start()
## Update the build volume visualization
def _onStackChangeTimerFinished(self):
if self._global_container_stack:
self._global_container_stack.propertyChanged.disconnect(self._onSettingPropertyChanged)
extruders = ExtruderManager.getInstance().getActiveExtruderStacks()

View File

@ -64,21 +64,21 @@ class MachineErrorChecker(QObject):
def _onMachineChanged(self) -> None:
if self._global_stack:
self._global_stack.propertyChanged.disconnect(self.startErrorCheck)
self._global_stack.propertyChanged.disconnect(self.startErrorCheckPropertyChanged)
self._global_stack.containersChanged.disconnect(self.startErrorCheck)
for extruder in self._global_stack.extruders.values():
extruder.propertyChanged.disconnect(self.startErrorCheck)
extruder.propertyChanged.disconnect(self.startErrorCheckPropertyChanged)
extruder.containersChanged.disconnect(self.startErrorCheck)
self._global_stack = self._machine_manager.activeMachine
if self._global_stack:
self._global_stack.propertyChanged.connect(self.startErrorCheck)
self._global_stack.propertyChanged.connect(self.startErrorCheckPropertyChanged)
self._global_stack.containersChanged.connect(self.startErrorCheck)
for extruder in self._global_stack.extruders.values():
extruder.propertyChanged.connect(self.startErrorCheck)
extruder.propertyChanged.connect(self.startErrorCheckPropertyChanged)
extruder.containersChanged.connect(self.startErrorCheck)
hasErrorUpdated = pyqtSignal()
@ -93,6 +93,13 @@ class MachineErrorChecker(QObject):
def needToWaitForResult(self) -> bool:
return self._need_to_check or self._check_in_progress
# Start the error check for property changed
# this is seperate from the startErrorCheck because it ignores a number property types
def startErrorCheckPropertyChanged(self, key, property_name):
if property_name != "value":
return
self.startErrorCheck()
# Starts the error check timer to schedule a new error check.
def startErrorCheck(self, *args) -> None:
if not self._check_in_progress:

View File

@ -1,5 +1,6 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional, Dict, Set
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty
from UM.Qt.ListModel import ListModel
@ -9,6 +10,9 @@ from UM.Qt.ListModel import ListModel
# Those 2 models are used by the material drop down menu to show generic materials and branded materials separately.
# The extruder position defined here is being used to bound a menu to the correct extruder. This is used in the top
# bar menu "Settings" -> "Extruder nr" -> "Material" -> this menu
from cura.Machines.MaterialNode import MaterialNode
class BaseMaterialsModel(ListModel):
extruderPositionChanged = pyqtSignal()
@ -54,8 +58,8 @@ class BaseMaterialsModel(ListModel):
self._extruder_position = 0
self._extruder_stack = None
self._available_materials = None
self._favorite_ids = None
self._available_materials = None # type: Optional[Dict[str, MaterialNode]]
self._favorite_ids = set() # type: Set[str]
def _updateExtruderStack(self):
global_stack = self._machine_manager.activeMachine
@ -102,8 +106,10 @@ class BaseMaterialsModel(ListModel):
return False
extruder_stack = global_stack.extruders[extruder_position]
self._available_materials = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack, extruder_stack)
available_materials = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack, extruder_stack)
if available_materials == self._available_materials:
return False
self._available_materials = available_materials
if self._available_materials is None:
return False

View File

@ -4,17 +4,14 @@
from UM.Logger import Logger
from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel
class FavoriteMaterialsModel(BaseMaterialsModel):
class FavoriteMaterialsModel(BaseMaterialsModel):
def __init__(self, parent = None):
super().__init__(parent)
self._update()
def _update(self):
# Perform standard check and reset if the check fails
if not self._canUpdate():
self.setItems([])
return
# Get updated list of favorites

View File

@ -11,10 +11,7 @@ class GenericMaterialsModel(BaseMaterialsModel):
self._update()
def _update(self):
# Perform standard check and reset if the check fails
if not self._canUpdate():
self.setItems([])
return
# Get updated list of favorites

View File

@ -28,12 +28,8 @@ class MaterialBrandsModel(BaseMaterialsModel):
self._update()
def _update(self):
# Perform standard check and reset if the check fails
if not self._canUpdate():
self.setItems([])
return
# Get updated list of favorites
self._favorite_ids = self._material_manager.getFavorites()

View File

@ -81,9 +81,14 @@ class AuthorizationHelpers:
# \param access_token: The encoded JWT token.
# \return: Dict containing some profile data.
def parseJWT(self, access_token: str) -> Optional["UserProfile"]:
token_request = requests.get("{}/check-token".format(self._settings.OAUTH_SERVER_URL), headers = {
"Authorization": "Bearer {}".format(access_token)
})
try:
token_request = requests.get("{}/check-token".format(self._settings.OAUTH_SERVER_URL), headers = {
"Authorization": "Bearer {}".format(access_token)
})
except ConnectionError:
# Connection was suddenly dropped. Nothing we can do about that.
Logger.logException("e", "Something failed while attempting to parse the JWT token")
return None
if token_request.status_code not in (200, 201):
Logger.log("w", "Could not retrieve token data from auth server: %s", token_request.text)
return None

View File

@ -44,7 +44,7 @@ class ConfigurationModel(QObject):
@pyqtProperty(str, fset = setBuildplateConfiguration, notify = configurationChanged)
def buildplateConfiguration(self) -> str:
return self._buildplate_configuration.capitalize()
return self._buildplate_configuration
## This method is intended to indicate whether the configuration is valid or not.
# The method checks if the mandatory fields are or not set

View File

@ -83,8 +83,9 @@ class ExtruderManager(QObject):
# \param index The index of the new active extruder.
@pyqtSlot(int)
def setActiveExtruderIndex(self, index: int) -> None:
self._active_extruder_index = index
self.activeExtruderChanged.emit()
if self._active_extruder_index != index:
self._active_extruder_index = index
self.activeExtruderChanged.emit()
@pyqtProperty(int, notify = activeExtruderChanged)
def activeExtruderIndex(self) -> int:
@ -344,6 +345,7 @@ class ExtruderManager(QObject):
if extruders_changed:
self.extrudersChanged.emit(global_stack_id)
self.setActiveExtruderIndex(0)
self.activeExtruderChanged.emit()
# After 3.4, all single-extrusion machines have their own extruder definition files instead of reusing
# "fdmextruder". We need to check a machine here so its extruder definition is correct according to this.

View File

@ -224,6 +224,6 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
"definition": ""
}
items.append(item)
self.setItems(items)
self.modelChanged.emit()
if self._items != items:
self.setItems(items)
self.modelChanged.emit()

View File

@ -3,8 +3,8 @@
from collections import defaultdict
import threading
from typing import Any, Dict, Optional, Set, TYPE_CHECKING
from PyQt5.QtCore import pyqtProperty, pyqtSlot
from typing import Any, Dict, Optional, Set, TYPE_CHECKING, List
from PyQt5.QtCore import pyqtProperty, pyqtSlot, pyqtSignal
from UM.Decorators import override
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
@ -42,13 +42,23 @@ class GlobalStack(CuraContainerStack):
# Per thread we have our own resolving_settings, or strange things sometimes occur.
self._resolving_settings = defaultdict(set) #type: Dict[str, Set[str]] # keys are thread names
extrudersChanged = pyqtSignal()
## Get the list of extruders of this stack.
#
# \return The extruders registered with this stack.
@pyqtProperty("QVariantMap")
@pyqtProperty("QVariantMap", notify = extrudersChanged)
def extruders(self) -> Dict[str, "ExtruderStack"]:
return self._extruders
@pyqtProperty("QVariantList", notify = extrudersChanged)
def extruderList(self) -> List["ExtruderStack"]:
result_tuple_list = sorted(list(self.extruders.items()), key=lambda x: int(x[0]))
result_list = [item[1] for item in result_tuple_list]
machine_extruder_count = self.getProperty("machine_extruder_count", "value")
return result_list[:machine_extruder_count]
@classmethod
def getLoadingPriority(cls) -> int:
return 2

View File

@ -203,7 +203,7 @@ class CuraEngineBackend(QObject, Backend):
@pyqtSlot()
def stopSlicing(self) -> None:
self.backendStateChange.emit(BackendState.NotStarted)
self.setState(BackendState.NotStarted)
if self._slicing: # We were already slicing. Stop the old job.
self._terminate()
self._createSocket()
@ -322,7 +322,7 @@ class CuraEngineBackend(QObject, Backend):
self._start_slice_job = None
if job.isCancelled() or job.getError() or job.getResult() == StartJobResult.Error:
self.backendStateChange.emit(BackendState.Error)
self.setState(BackendState.Error)
self.backendError.emit(job)
return
@ -331,10 +331,10 @@ class CuraEngineBackend(QObject, Backend):
self._error_message = Message(catalog.i18nc("@info:status",
"Unable to slice with the current material as it is incompatible with the selected machine or configuration."), title = catalog.i18nc("@info:title", "Unable to slice"))
self._error_message.show()
self.backendStateChange.emit(BackendState.Error)
self.setState(BackendState.Error)
self.backendError.emit(job)
else:
self.backendStateChange.emit(BackendState.NotStarted)
self.setState(BackendState.NotStarted)
return
if job.getResult() == StartJobResult.SettingError:
@ -362,10 +362,10 @@ class CuraEngineBackend(QObject, Backend):
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice with the current settings. The following settings have errors: {0}").format(", ".join(error_labels)),
title = catalog.i18nc("@info:title", "Unable to slice"))
self._error_message.show()
self.backendStateChange.emit(BackendState.Error)
self.setState(BackendState.Error)
self.backendError.emit(job)
else:
self.backendStateChange.emit(BackendState.NotStarted)
self.setState(BackendState.NotStarted)
return
elif job.getResult() == StartJobResult.ObjectSettingError:
@ -386,7 +386,7 @@ class CuraEngineBackend(QObject, Backend):
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice due to some per-model settings. The following settings have errors on one or more models: {error_labels}").format(error_labels = ", ".join(errors.values())),
title = catalog.i18nc("@info:title", "Unable to slice"))
self._error_message.show()
self.backendStateChange.emit(BackendState.Error)
self.setState(BackendState.Error)
self.backendError.emit(job)
return
@ -395,16 +395,16 @@ class CuraEngineBackend(QObject, Backend):
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice because the prime tower or prime position(s) are invalid."),
title = catalog.i18nc("@info:title", "Unable to slice"))
self._error_message.show()
self.backendStateChange.emit(BackendState.Error)
self.setState(BackendState.Error)
self.backendError.emit(job)
else:
self.backendStateChange.emit(BackendState.NotStarted)
self.setState(BackendState.NotStarted)
if job.getResult() == StartJobResult.ObjectsWithDisabledExtruder:
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice because there are objects associated with disabled Extruder %s." % job.getMessage()),
title = catalog.i18nc("@info:title", "Unable to slice"))
self._error_message.show()
self.backendStateChange.emit(BackendState.Error)
self.setState(BackendState.Error)
self.backendError.emit(job)
return
@ -413,10 +413,10 @@ class CuraEngineBackend(QObject, Backend):
self._error_message = Message(catalog.i18nc("@info:status", "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit."),
title = catalog.i18nc("@info:title", "Unable to slice"))
self._error_message.show()
self.backendStateChange.emit(BackendState.Error)
self.setState(BackendState.Error)
self.backendError.emit(job)
else:
self.backendStateChange.emit(BackendState.NotStarted)
self.setState(BackendState.NotStarted)
self._invokeSlice()
return
@ -424,7 +424,7 @@ class CuraEngineBackend(QObject, Backend):
self._socket.sendMessage(job.getSliceMessage())
# Notify the user that it's now up to the backend to do it's job
self.backendStateChange.emit(BackendState.Processing)
self.setState(BackendState.Processing)
if self._slice_start_time:
Logger.log("d", "Sending slice message took %s seconds", time() - self._slice_start_time )
@ -442,7 +442,7 @@ class CuraEngineBackend(QObject, Backend):
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
if node.callDecoration("isBlockSlicing"):
enable_timer = False
self.backendStateChange.emit(BackendState.Disabled)
self.setState(BackendState.Disabled)
self._is_disabled = True
gcode_list = node.callDecoration("getGCodeList")
if gcode_list is not None:
@ -451,7 +451,7 @@ class CuraEngineBackend(QObject, Backend):
if self._use_timer == enable_timer:
return self._use_timer
if enable_timer:
self.backendStateChange.emit(BackendState.NotStarted)
self.setState(BackendState.NotStarted)
self.enableTimer()
return True
else:
@ -518,7 +518,7 @@ class CuraEngineBackend(QObject, Backend):
self._build_plates_to_be_sliced.append(build_plate_number)
self.printDurationMessage.emit(source_build_plate_number, {}, [])
self.processingProgress.emit(0.0)
self.backendStateChange.emit(BackendState.NotStarted)
self.setState(BackendState.NotStarted)
# if not self._use_timer:
# With manually having to slice, we want to clear the old invalid layer data.
self._clearLayerData(build_plate_changed)
@ -567,7 +567,7 @@ class CuraEngineBackend(QObject, Backend):
self.stopSlicing()
self.markSliceAll()
self.processingProgress.emit(0.0)
self.backendStateChange.emit(BackendState.NotStarted)
self.setState(BackendState.NotStarted)
if not self._use_timer:
# With manually having to slice, we want to clear the old invalid layer data.
self._clearLayerData()
@ -613,7 +613,7 @@ class CuraEngineBackend(QObject, Backend):
# \param message The protobuf message containing the slicing progress.
def _onProgressMessage(self, message: Arcus.PythonMessage) -> None:
self.processingProgress.emit(message.amount)
self.backendStateChange.emit(BackendState.Processing)
self.setState(BackendState.Processing)
def _invokeSlice(self) -> None:
if self._use_timer:
@ -632,7 +632,7 @@ class CuraEngineBackend(QObject, Backend):
#
# \param message The protobuf message signalling that slicing is finished.
def _onSlicingFinishedMessage(self, message: Arcus.PythonMessage) -> None:
self.backendStateChange.emit(BackendState.Done)
self.setState(BackendState.Done)
self.processingProgress.emit(1.0)
gcode_list = self._scene.gcode_dict[self._start_slice_job_build_plate] #type: ignore #Because we generate this attribute dynamically.

View File

@ -323,7 +323,7 @@ class StartSliceJob(Job):
value = stack.getProperty(key, "value")
result[key] = value
Job.yieldThread()
result["print_bed_temperature"] = result["material_bed_temperature"] # Renamed settings.
result["print_temperature"] = result["material_print_temperature"]
result["time"] = time.strftime("%H:%M:%S") #Some extra settings.

View File

@ -14,8 +14,8 @@ Window
modality: Qt.ApplicationModal
flags: Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowCloseButtonHint
width: 720 * screenScaleFactor
height: 640 * screenScaleFactor
width: Math.floor(720 * screenScaleFactor)
height: Math.floor(640 * screenScaleFactor)
minimumWidth: width
maximumWidth: minimumWidth
minimumHeight: height
@ -95,6 +95,7 @@ Window
licenseDialog.show();
}
}
ToolboxLicenseDialog
{
id: licenseDialog

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.3
import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
@ -59,6 +59,7 @@ Item
wrapMode: Text.WordWrap
width: parent.width
height: UM.Theme.getSize("toolbox_property_label").height
renderType: Text.NativeRendering
}
Label
{
@ -70,6 +71,7 @@ Item
left: title.left
topMargin: UM.Theme.getSize("default_margin").height
}
renderType: Text.NativeRendering
}
Column
{
@ -88,12 +90,14 @@ Item
text: catalog.i18nc("@label", "Website") + ":"
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text_medium")
renderType: Text.NativeRendering
}
Label
{
text: catalog.i18nc("@label", "Email") + ":"
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text_medium")
renderType: Text.NativeRendering
}
}
Column
@ -122,6 +126,7 @@ Item
color: UM.Theme.getColor("text")
linkColor: UM.Theme.getColor("text_link")
onLinkActivated: Qt.openUrlExternally(link)
renderType: Text.NativeRendering
}
Label
@ -138,6 +143,7 @@ Item
color: UM.Theme.getColor("text")
linkColor: UM.Theme.getColor("text_link")
onLinkActivated: Qt.openUrlExternally(link)
renderType: Text.NativeRendering
}
}
Rectangle

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
@ -64,6 +64,7 @@ Item
font: UM.Theme.getFont("default_bold")
horizontalAlignment: Text.AlignRight
width: control.width
renderType: Text.NativeRendering
}
}
}

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
@ -67,6 +67,7 @@ Item
wrapMode: Text.WordWrap
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("medium")
renderType: Text.NativeRendering
}
TableView
@ -99,6 +100,7 @@ Item
text: styleData.value || ""
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("default_bold")
renderType: Text.NativeRendering
}
Rectangle
{
@ -118,6 +120,7 @@ Item
text: styleData.value || ""
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
}
}
itemDelegate: Item
@ -130,6 +133,7 @@ Item
text: styleData.value || ""
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
}
}
@ -144,6 +148,7 @@ Item
elide: Text.ElideRight
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
}
}
@ -232,5 +237,6 @@ Item
color: UM.Theme.getColor("text")
linkColor: UM.Theme.getColor("text_link")
onLinkActivated: Qt.openUrlExternally(link)
renderType: Text.NativeRendering
}
}

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick 2.10
import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.1
import QtQuick.Layouts 1.1
@ -66,6 +66,7 @@ UM.Dialog
anchors.right: parent.right
font: UM.Theme.getFont("default")
wrapMode: Text.WordWrap
renderType: Text.NativeRendering
}
// Buttons

View File

@ -26,10 +26,19 @@ Item
}
height: childrenRect.height + 2 * UM.Theme.getSize("wide_margin").height
spacing: UM.Theme.getSize("default_margin").height
Repeater
{
model: toolbox.packagesModel
delegate: ToolboxDetailTile {}
delegate: Loader
{
// FIXME: When using asynchronous loading, on Mac and Windows, the tile may fail to load complete,
// leaving an empty space below the title part. We turn it off for now to make it work on Mac and
// Windows.
// Can be related to this QT bug: https://bugreports.qt.io/browse/QTBUG-50992
asynchronous: false
source: "ToolboxDetailTile.qml"
}
}
}
}

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.3
import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
@ -65,6 +65,7 @@ Item
wrapMode: Text.WordWrap
width: parent.width
height: UM.Theme.getSize("toolbox_property_label").height
renderType: Text.NativeRendering
}
Column
@ -84,24 +85,28 @@ Item
text: catalog.i18nc("@label", "Version") + ":"
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text_medium")
renderType: Text.NativeRendering
}
Label
{
text: catalog.i18nc("@label", "Last updated") + ":"
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text_medium")
renderType: Text.NativeRendering
}
Label
{
text: catalog.i18nc("@label", "Author") + ":"
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text_medium")
renderType: Text.NativeRendering
}
Label
{
text: catalog.i18nc("@label", "Downloads") + ":"
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text_medium")
renderType: Text.NativeRendering
}
}
Column
@ -121,6 +126,7 @@ Item
text: details === null ? "" : (details.version || catalog.i18nc("@label", "Unknown"))
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
renderType: Text.NativeRendering
}
Label
{
@ -135,6 +141,7 @@ Item
}
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
renderType: Text.NativeRendering
}
Label
{
@ -153,12 +160,14 @@ Item
color: UM.Theme.getColor("text")
linkColor: UM.Theme.getColor("text_link")
onLinkActivated: Qt.openUrlExternally(link)
renderType: Text.NativeRendering
}
Label
{
text: details === null ? "" : (details.download_count || catalog.i18nc("@label", "Unknown"))
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
renderType: Text.NativeRendering
}
}
Rectangle

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
@ -31,6 +31,7 @@ Item
wrapMode: Text.WordWrap
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("medium_bold")
renderType: Text.NativeRendering
}
Label
{
@ -42,6 +43,7 @@ Item
wrapMode: Text.WordWrap
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
}
}

View File

@ -1,40 +1,69 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
import Cura 1.1 as Cura
Column
{
property bool installed: toolbox.isInstalled(model.id)
property bool canUpdate: toolbox.canUpdate(model.id)
property bool loginRequired: model.login_required && !Cura.API.account.isLoggedIn
width: UM.Theme.getSize("toolbox_action_button").width
spacing: UM.Theme.getSize("narrow_margin").height
ToolboxProgressButton
Item
{
id: installButton
active: toolbox.isDownloading && toolbox.activePackage == model
complete: installed
readyAction: function()
width: installButton.width
height: installButton.height
ToolboxProgressButton
{
toolbox.activePackage = model
toolbox.startDownload(model.download_url)
id: installButton
active: toolbox.isDownloading && toolbox.activePackage == model
onReadyAction:
{
toolbox.activePackage = model
toolbox.startDownload(model.download_url)
}
onActiveAction: toolbox.cancelDownload()
// Don't allow installing while another download is running
enabled: installed || (!(toolbox.isDownloading && toolbox.activePackage != model) && !loginRequired)
opacity: enabled ? 1.0 : 0.5
visible: !updateButton.visible && !installed// Don't show when the update button is visible
}
activeAction: function()
Cura.SecondaryButton
{
toolbox.cancelDownload()
visible: installed
onClicked: toolbox.viewCategory = "installed"
text: catalog.i18nc("@action:button", "Installed")
fixedWidthMode: true
width: installButton.width
height: installButton.height
}
completeAction: function()
}
Label
{
wrapMode: Text.WordWrap
text: catalog.i18nc("@label:The string between <a href=> and </a> is the highlighted link", "<a href='%1'>Log in</a> is required to install or update")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
linkColor: UM.Theme.getColor("text_link")
visible: loginRequired
width: installButton.width
renderType: Text.NativeRendering
MouseArea
{
toolbox.viewCategory = "installed"
anchors.fill: parent
onClicked: Cura.API.account.login()
}
// Don't allow installing while another download is running
enabled: installed || !(toolbox.isDownloading && toolbox.activePackage != model)
opacity: enabled ? 1.0 : 0.5
visible: !updateButton.visible // Don't show when the update button is visible
}
ToolboxProgressButton
@ -44,20 +73,19 @@ Column
readyLabel: catalog.i18nc("@action:button", "Update")
activeLabel: catalog.i18nc("@action:button", "Updating")
completeLabel: catalog.i18nc("@action:button", "Updated")
readyAction: function()
onReadyAction:
{
toolbox.activePackage = model
toolbox.update(model.id)
}
activeAction: function()
{
toolbox.cancelDownload()
}
onActiveAction: toolbox.cancelDownload()
// Don't allow installing while another download is running
enabled: !(toolbox.isDownloading && toolbox.activePackage != model)
enabled: !(toolbox.isDownloading && toolbox.activePackage != model) && !loginRequired
opacity: enabled ? 1.0 : 0.5
visible: canUpdate
}
Connections
{
target: toolbox

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtQuick.Layouts 1.3
@ -23,8 +23,9 @@ Column
width: parent.width
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("medium")
renderType: Text.NativeRendering
}
GridLayout
Grid
{
id: grid
width: parent.width - 2 * parent.padding
@ -34,10 +35,12 @@ Column
Repeater
{
model: gridArea.model
delegate: ToolboxDownloadsGridTile
delegate: Loader
{
Layout.preferredWidth: (grid.width - (grid.columns - 1) * grid.columnSpacing) / grid.columns
Layout.preferredHeight: UM.Theme.getSize("toolbox_thumbnail_small").height
asynchronous: true
width: Math.round((grid.width - (grid.columns - 1) * grid.columnSpacing) / grid.columns)
height: UM.Theme.getSize("toolbox_thumbnail_small").height
source: "ToolboxDownloadsGridTile.qml"
}
}
}

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.3
import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtQuick.Layouts 1.3
@ -62,6 +62,8 @@ Item
{
width: parent.width - thumbnail.width - parent.spacing
spacing: Math.floor(UM.Theme.getSize("narrow_margin").width)
anchors.top: parent.top
anchors.topMargin: UM.Theme.getSize("default_margin").height
Label
{
id: name
@ -70,6 +72,7 @@ Item
wrapMode: Text.WordWrap
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("default_bold")
renderType: Text.NativeRendering
}
Label
{
@ -81,6 +84,7 @@ Item
wrapMode: Text.WordWrap
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
}
}
}

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
@ -24,29 +24,33 @@ Rectangle
width: parent.width
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("medium")
renderType: Text.NativeRendering
}
Grid
{
height: childrenRect.height
spacing: UM.Theme.getSize("wide_margin").width
columns: 3
anchors
{
horizontalCenter: parent.horizontalCenter
}
anchors.horizontalCenter: parent.horizontalCenter
Repeater
{
model: {
if ( toolbox.viewCategory == "plugin" )
model:
{
if (toolbox.viewCategory == "plugin")
{
return toolbox.pluginsShowcaseModel
}
if ( toolbox.viewCategory == "material" )
if (toolbox.viewCategory == "material")
{
return toolbox.materialsShowcaseModel
}
}
delegate: ToolboxDownloadsShowcaseTile {}
delegate: Loader
{
asynchronous: true
source: "ToolboxDownloadsShowcaseTile.qml"
}
}
}
}

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtGraphicalEffects 1.0
@ -79,6 +79,7 @@ Rectangle
wrapMode: Text.WordWrap
color: UM.Theme.getColor("button_text")
font: UM.Theme.getFont("medium_bold")
renderType: Text.NativeRendering
}
}
MouseArea

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
@ -18,5 +18,6 @@ Rectangle
{
centerIn: parent
}
renderType: Text.NativeRendering
}
}

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
@ -26,7 +26,7 @@ Item
right: restartButton.right
rightMargin: UM.Theme.getSize("default_margin").width
}
renderType: Text.NativeRendering
}
Button
{
@ -56,6 +56,7 @@ Item
text: control.text
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
renderType: Text.NativeRendering
}
}
}

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick 2.10
import QtQuick.Dialogs 1.1
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
@ -21,44 +21,40 @@ ScrollView
Column
{
spacing: UM.Theme.getSize("default_margin").height
visible: toolbox.pluginsInstalledModel.items.length > 0
height: childrenRect.height + 4 * UM.Theme.getSize("default_margin").height
anchors
{
right: parent.right
left: parent.left
leftMargin: UM.Theme.getSize("wide_margin").width
topMargin: UM.Theme.getSize("wide_margin").height
bottomMargin: UM.Theme.getSize("wide_margin").height
margins: UM.Theme.getSize("default_margin").width
top: parent.top
}
height: childrenRect.height + 4 * UM.Theme.getSize("default_margin").height
Label
{
visible: toolbox.pluginsInstalledModel.items.length > 0
width: parent.width
width: page.width
text: catalog.i18nc("@title:tab", "Plugins")
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("medium")
renderType: Text.NativeRendering
}
Rectangle
{
visible: toolbox.pluginsInstalledModel.items.length > 0
color: "transparent"
width: parent.width
height: childrenRect.height + 1 * UM.Theme.getSize("default_lining").width
height: childrenRect.height + UM.Theme.getSize("default_margin").width
border.color: UM.Theme.getColor("lining")
border.width: UM.Theme.getSize("default_lining").width
Column
{
height: childrenRect.height
anchors
{
top: parent.top
right: parent.right
left: parent.left
leftMargin: UM.Theme.getSize("default_margin").width
rightMargin: UM.Theme.getSize("default_margin").width
topMargin: UM.Theme.getSize("default_lining").width
bottomMargin: UM.Theme.getSize("default_lining").width
margins: UM.Theme.getSize("default_margin").width
}
Repeater
{
@ -70,32 +66,27 @@ ScrollView
}
Label
{
visible: toolbox.materialsInstalledModel.items.length > 0
width: page.width
text: catalog.i18nc("@title:tab", "Materials")
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("medium")
renderType: Text.NativeRendering
}
Rectangle
{
visible: toolbox.materialsInstalledModel.items.length > 0
color: "transparent"
width: parent.width
height: childrenRect.height + 1 * UM.Theme.getSize("default_lining").width
height: childrenRect.height + UM.Theme.getSize("default_margin").width
border.color: UM.Theme.getColor("lining")
border.width: UM.Theme.getSize("default_lining").width
Column
{
height: Math.max( UM.Theme.getSize("wide_margin").height, childrenRect.height)
anchors
{
top: parent.top
right: parent.right
left: parent.left
leftMargin: UM.Theme.getSize("default_margin").width
rightMargin: UM.Theme.getSize("default_margin").width
topMargin: UM.Theme.getSize("default_lining").width
bottomMargin: UM.Theme.getSize("default_lining").width
margins: UM.Theme.getSize("default_margin").width
}
Repeater
{

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
@ -51,6 +51,7 @@ Item
wrapMode: Text.WordWrap
font: UM.Theme.getFont("default_bold")
color: pluginInfo.color
renderType: Text.NativeRendering
}
Label
{
@ -60,6 +61,7 @@ Item
width: parent.width
wrapMode: Text.WordWrap
color: pluginInfo.color
renderType: Text.NativeRendering
}
}
Column
@ -88,6 +90,7 @@ Item
onLinkActivated: Qt.openUrlExternally("mailto:" + model.author_email + "?Subject=Cura: " + model.name + " Plugin")
color: model.enabled ? UM.Theme.getColor("text") : UM.Theme.getColor("lining")
linkColor: UM.Theme.getColor("text_link")
renderType: Text.NativeRendering
}
Label
@ -98,6 +101,7 @@ Item
color: UM.Theme.getColor("text")
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
renderType: Text.NativeRendering
}
}
ToolboxInstalledTileActions

View File

@ -1,15 +1,18 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
import Cura 1.1 as Cura
Column
{
property bool canUpdate: false
property bool canDowngrade: false
property bool loginRequired: model.login_required && !Cura.API.account.isLoggedIn
width: UM.Theme.getSize("toolbox_action_button").width
spacing: UM.Theme.getSize("narrow_margin").height
@ -21,6 +24,7 @@ Column
font: UM.Theme.getFont("default")
wrapMode: Text.WordWrap
width: parent.width
renderType: Text.NativeRendering
}
ToolboxProgressButton
@ -30,59 +34,49 @@ Column
readyLabel: catalog.i18nc("@action:button", "Update")
activeLabel: catalog.i18nc("@action:button", "Updating")
completeLabel: catalog.i18nc("@action:button", "Updated")
readyAction: function()
onReadyAction:
{
toolbox.activePackage = model
toolbox.update(model.id)
}
activeAction: function()
{
toolbox.cancelDownload()
}
onActiveAction: toolbox.cancelDownload()
// Don't allow installing while another download is running
enabled: !(toolbox.isDownloading && toolbox.activePackage != model)
enabled: !(toolbox.isDownloading && toolbox.activePackage != model) && !loginRequired
opacity: enabled ? 1.0 : 0.5
visible: canUpdate
}
Button
Label
{
wrapMode: Text.WordWrap
text: catalog.i18nc("@label:The string between <a href=> and </a> is the highlighted link", "<a href='%1'>Log in</a> is required to update")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
linkColor: UM.Theme.getColor("text_link")
visible: loginRequired
width: updateButton.width
renderType: Text.NativeRendering
MouseArea
{
anchors.fill: parent
onClicked: Cura.API.account.login()
}
}
Cura.SecondaryButton
{
id: removeButton
text: canDowngrade ? catalog.i18nc("@action:button", "Downgrade") : catalog.i18nc("@action:button", "Uninstall")
visible: !model.is_bundled && model.is_installed
enabled: !toolbox.isDownloading
style: ButtonStyle
{
background: Rectangle
{
implicitWidth: UM.Theme.getSize("toolbox_action_button").width
implicitHeight: UM.Theme.getSize("toolbox_action_button").height
color: "transparent"
border
{
width: UM.Theme.getSize("default_lining").width
color:
{
if (control.hovered)
{
return UM.Theme.getColor("primary_hover")
}
else
{
return UM.Theme.getColor("lining")
}
}
}
}
label: Label
{
text: control.text
color: UM.Theme.getColor("text")
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
font: UM.Theme.getFont("default")
}
}
width: UM.Theme.getSize("toolbox_action_button").width
height: UM.Theme.getSize("toolbox_action_button").height
fixedWidthMode: true
onClicked: toolbox.checkPackageUsageAndUninstall(model.id)
Connections
{

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick 2.10
import QtQuick.Dialogs 1.1
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
@ -32,6 +32,7 @@ UM.Dialog
anchors.right: parent.right
text: licenseDialog.pluginName + catalog.i18nc("@label", "This plugin contains a license.\nYou need to accept this license to install this plugin.\nDo you agree with the terms below?")
wrapMode: Text.Wrap
renderType: Text.NativeRendering
}
TextArea
{

View File

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
@ -18,5 +18,6 @@ Rectangle
{
centerIn: parent
}
renderType: Text.NativeRendering
}
}

View File

@ -5,6 +5,7 @@ import QtQuick 2.2
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
import Cura 1.0 as Cura
Item
@ -18,16 +19,19 @@ Item
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)
signal readyAction() // Action when button is ready and clicked (likely install)
signal activeAction() // Action when button is active and clicked (likely cancel)
signal completeAction() // 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
Cura.PrimaryButton
{
id: button
width: UM.Theme.getSize("toolbox_action_button").width
height: UM.Theme.getSize("toolbox_action_button").height
fixedWidthMode: true
text:
{
if (complete)
@ -47,101 +51,15 @@ Item
{
if (complete)
{
return completeAction()
completeAction()
}
else if (active)
{
return activeAction()
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 "transparent"
}
else
{
if (control.hovered)
{
return UM.Theme.getColor("primary_hover")
}
else
{
return UM.Theme.getColor("primary")
}
}
}
border
{
width:
{
if (base.complete)
{
UM.Theme.getSize("default_lining").width
}
else
{
return 0
}
}
color:
{
if (control.hovered)
{
return UM.Theme.getColor("primary_hover")
}
else
{
return UM.Theme.getColor("lining")
}
}
}
}
label: Label
{
text: control.text
color:
{
if (base.complete)
{
return UM.Theme.getColor("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:
{
if (base.complete)
{
return UM.Theme.getFont("default")
}
else
{
return UM.Theme.getFont("default_bold")
}
}
readyAction()
}
}
}

View File

@ -1,51 +1,51 @@
// 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 QtQuick 2.10
import QtQuick.Controls 2.3
import UM 1.1 as UM
Button
{
id: control
property bool active: false
style: ButtonStyle
hoverEnabled: true
background: Item
{
background: Rectangle
implicitWidth: UM.Theme.getSize("toolbox_header_tab").width
implicitHeight: UM.Theme.getSize("toolbox_header_tab").height
Rectangle
{
color: "transparent"
implicitWidth: UM.Theme.getSize("toolbox_header_tab").width
implicitHeight: UM.Theme.getSize("toolbox_header_tab").height
Rectangle
{
visible: control.active
color: UM.Theme.getColor("toolbox_header_highlight_hover")
anchors.bottom: parent.bottom
width: parent.width
height: UM.Theme.getSize("toolbox_header_highlight").height
}
}
label: Label
{
text: control.text
color:
{
if(control.hovered)
{
return UM.Theme.getColor("toolbox_header_button_text_hovered");
}
if(control.active)
{
return UM.Theme.getColor("toolbox_header_button_text_active");
}
else
{
return UM.Theme.getColor("toolbox_header_button_text_inactive");
}
}
font: control.enabled ? (control.active ? UM.Theme.getFont("medium_bold") : UM.Theme.getFont("medium")) : UM.Theme.getFont("default_italic")
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
visible: control.active
color: UM.Theme.getColor("primary")
anchors.bottom: parent.bottom
width: parent.width
height: UM.Theme.getSize("toolbox_header_highlight").height
}
}
}
contentItem: Label
{
id: label
text: control.text
color:
{
if(control.hovered)
{
return UM.Theme.getColor("toolbox_header_button_text_hovered");
}
if(control.active)
{
return UM.Theme.getColor("toolbox_header_button_text_active");
}
else
{
return UM.Theme.getColor("toolbox_header_button_text_inactive");
}
}
font: control.enabled ? (control.active ? UM.Theme.getFont("medium_bold") : UM.Theme.getFont("medium")) : UM.Theme.getFont("default_italic")
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
renderType: Text.NativeRendering
}
}

View File

@ -2,18 +2,19 @@
# Cura is released under the terms of the LGPLv3 or higher.
import re
from typing import Dict
from typing import Dict, List, Optional, Union
from PyQt5.QtCore import Qt, pyqtProperty, pyqtSignal
from UM.Qt.ListModel import ListModel
## Model that holds cura packages. By setting the filter property the instances held by this model can be changed.
class AuthorsModel(ListModel):
def __init__(self, parent = None):
def __init__(self, parent = None) -> None:
super().__init__(parent)
self._metadata = None
self._metadata = None # type: Optional[List[Dict[str, Union[str, List[str], int]]]]
self.addRoleName(Qt.UserRole + 1, "id")
self.addRoleName(Qt.UserRole + 2, "name")
@ -25,39 +26,40 @@ class AuthorsModel(ListModel):
self.addRoleName(Qt.UserRole + 8, "description")
# List of filters for queries. The result is the union of the each list of results.
self._filter = {} # type: Dict[str,str]
self._filter = {} # type: Dict[str, str]
def setMetadata(self, data):
self._metadata = data
self._update()
def setMetadata(self, data: List[Dict[str, Union[str, List[str], int]]]):
if self._metadata != data:
self._metadata = data
self._update()
def _update(self):
items = []
def _update(self) -> None:
items = [] # type: List[Dict[str, Union[str, List[str], int, None]]]
if not self._metadata:
self.setItems([])
self.setItems(items)
return
for author in self._metadata:
items.append({
"id": author["author_id"],
"name": author["display_name"],
"email": author["email"] if "email" in author else None,
"website": author["website"],
"package_count": author["package_count"] if "package_count" in author else 0,
"package_types": author["package_types"] if "package_types" in author else [],
"icon_url": author["icon_url"] if "icon_url" in author else None,
"description": "Material and quality profiles from {author_name}".format(author_name = author["display_name"])
"id": author.get("author_id"),
"name": author.get("display_name"),
"email": author.get("email"),
"website": author.get("website"),
"package_count": author.get("package_count", 0),
"package_types": author.get("package_types", []),
"icon_url": author.get("icon_url"),
"description": "Material and quality profiles from {author_name}".format(author_name = author.get("display_name", ""))
})
# Filter on all the key-word arguments.
for key, value in self._filter.items():
if key is "package_types":
key_filter = lambda item, value = value: value in item["package_types"]
key_filter = lambda item, value = value: value in item["package_types"] # type: ignore
elif "*" in value:
key_filter = lambda item, key = key, value = value: self._matchRegExp(item, key, value)
key_filter = lambda item, key = key, value = value: self._matchRegExp(item, key, value) # type: ignore
else:
key_filter = lambda item, key = key, value = value: self._matchString(item, key, value)
items = filter(key_filter, items)
key_filter = lambda item, key = key, value = value: self._matchString(item, key, value) # type: ignore
items = filter(key_filter, items) # type: ignore
# Execute all filters.
filtered_items = list(items)

View File

@ -33,20 +33,22 @@ class PackagesModel(ListModel):
self.addRoleName(Qt.UserRole + 12, "last_updated")
self.addRoleName(Qt.UserRole + 13, "is_bundled")
self.addRoleName(Qt.UserRole + 14, "is_active")
self.addRoleName(Qt.UserRole + 15, "is_installed") # Scheduled pkgs are included in the model but should not be marked as actually installed
self.addRoleName(Qt.UserRole + 15, "is_installed") # Scheduled pkgs are included in the model but should not be marked as actually installed
self.addRoleName(Qt.UserRole + 16, "has_configs")
self.addRoleName(Qt.UserRole + 17, "supported_configs")
self.addRoleName(Qt.UserRole + 18, "download_count")
self.addRoleName(Qt.UserRole + 19, "tags")
self.addRoleName(Qt.UserRole + 20, "links")
self.addRoleName(Qt.UserRole + 21, "website")
self.addRoleName(Qt.UserRole + 22, "login_required")
# List of filters for queries. The result is the union of the each list of results.
self._filter = {} # type: Dict[str, str]
def setMetadata(self, data):
self._metadata = data
self._update()
if self._metadata != data:
self._metadata = data
self._update()
def _update(self):
items = []
@ -99,6 +101,7 @@ class PackagesModel(ListModel):
"tags": package["tags"] if "tags" in package else [],
"links": links_dict,
"website": package["website"] if "website" in package else None,
"login_required": "login-required" in package.get("tags", [])
})
# Filter on all the key-word arguments.

View File

@ -31,8 +31,8 @@ i18n_catalog = i18nCatalog("cura")
## The Toolbox class is responsible of communicating with the server through the API
class Toolbox(QObject, Extension):
DEFAULT_CLOUD_API_ROOT = "https://api.ultimaker.com" #type: str
DEFAULT_CLOUD_API_VERSION = 1 #type: int
DEFAULT_CLOUD_API_ROOT = "https://api.ultimaker.com" # type: str
DEFAULT_CLOUD_API_VERSION = 1 # type: int
def __init__(self, application: CuraApplication) -> None:
super().__init__()
@ -66,31 +66,26 @@ class Toolbox(QObject, Extension):
self._old_plugin_ids = set() # type: Set[str]
self._old_plugin_metadata = dict() # type: Dict[str, Dict[str, Any]]
# Data:
self._metadata = {
# The responses as given by the server parsed to a list.
self._server_response_data = {
"authors": [],
"packages": [],
"plugins_showcase": [],
"plugins_available": [],
"plugins_installed": [],
"materials_showcase": [],
"materials_available": [],
"materials_installed": [],
"materials_generic": []
"packages": []
} # type: Dict[str, List[Any]]
# Models:
self._models = {
"authors": AuthorsModel(self),
"packages": PackagesModel(self),
"plugins_showcase": PackagesModel(self),
"plugins_available": PackagesModel(self),
"plugins_installed": PackagesModel(self),
"materials_showcase": AuthorsModel(self),
"materials_available": AuthorsModel(self),
"materials_installed": PackagesModel(self),
"materials_generic": PackagesModel(self)
} # type: Dict[str, ListModel]
} # type: Dict[str, Union[AuthorsModel, PackagesModel]]
self._plugins_showcase_model = PackagesModel(self)
self._plugins_available_model = PackagesModel(self)
self._plugins_installed_model = PackagesModel(self)
self._materials_showcase_model = AuthorsModel(self)
self._materials_available_model = AuthorsModel(self)
self._materials_installed_model = PackagesModel(self)
self._materials_generic_model = PackagesModel(self)
# These properties are for keeping track of the UI state:
# ----------------------------------------------------------------------
@ -178,12 +173,7 @@ class Toolbox(QObject, Extension):
)
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)),
"plugins_showcase": QUrl("{base_url}/showcase".format(base_url = self._api_url)),
"plugins_available": QUrl("{base_url}/packages?package_type=plugin".format(base_url = self._api_url)),
"materials_showcase": QUrl("{base_url}/showcase".format(base_url = self._api_url)),
"materials_available": QUrl("{base_url}/packages?package_type=material".format(base_url = self._api_url)),
"materials_generic": QUrl("{base_url}/packages?package_type=material&tags=generic".format(base_url = self._api_url))
"packages": QUrl("{base_url}/packages".format(base_url = self._api_url))
}
# Get the API root for the packages API depending on Cura version settings.
@ -192,9 +182,9 @@ class Toolbox(QObject, Extension):
return self.DEFAULT_CLOUD_API_ROOT
if not hasattr(cura.CuraVersion, "CuraCloudAPIRoot"): # type: ignore
return self.DEFAULT_CLOUD_API_ROOT
if not cura.CuraVersion.CuraCloudAPIRoot: # type: ignore
if not cura.CuraVersion.CuraCloudAPIRoot: # type: ignore
return self.DEFAULT_CLOUD_API_ROOT
return cura.CuraVersion.CuraCloudAPIRoot # type: ignore
return cura.CuraVersion.CuraCloudAPIRoot # type: ignore
# Get the cloud API version from CuraVersion
def _getCloudAPIVersion(self) -> int:
@ -202,9 +192,9 @@ class Toolbox(QObject, Extension):
return self.DEFAULT_CLOUD_API_VERSION
if not hasattr(cura.CuraVersion, "CuraCloudAPIVersion"): # type: ignore
return self.DEFAULT_CLOUD_API_VERSION
if not cura.CuraVersion.CuraCloudAPIVersion: # type: ignore
if not cura.CuraVersion.CuraCloudAPIVersion: # type: ignore
return self.DEFAULT_CLOUD_API_VERSION
return cura.CuraVersion.CuraCloudAPIVersion # type: ignore
return cura.CuraVersion.CuraCloudAPIVersion # type: ignore
# Get the packages version depending on Cura version settings.
def _getSDKVersion(self) -> Union[int, str]:
@ -231,12 +221,6 @@ class Toolbox(QObject, Extension):
# Make remote requests:
self._makeRequestByType("packages")
self._makeRequestByType("authors")
# TODO: Uncomment in the future when the tag-filtered api calls work in the cloud server
# self._makeRequestByType("plugins_showcase")
# self._makeRequestByType("plugins_available")
# self._makeRequestByType("materials_showcase")
# self._makeRequestByType("materials_available")
# self._makeRequestByType("materials_generic")
# Gather installed packages:
self._updateInstalledModels()
@ -281,7 +265,7 @@ class Toolbox(QObject, Extension):
"description": plugin_data["plugin"]["description"]
}
return formatted
except:
except KeyError:
Logger.log("w", "Unable to convert plugin meta data %s", str(plugin_data))
return None
@ -319,13 +303,10 @@ class Toolbox(QObject, Extension):
if plugin_id not in all_plugin_package_ids)
self._old_plugin_metadata = {k: v for k, v in self._old_plugin_metadata.items() if k in self._old_plugin_ids}
self._metadata["plugins_installed"] = all_packages["plugin"] + list(self._old_plugin_metadata.values())
self._models["plugins_installed"].setMetadata(self._metadata["plugins_installed"])
self._plugins_installed_model.setMetadata(all_packages["plugin"] + list(self._old_plugin_metadata.values()))
self.metadataChanged.emit()
if "material" in all_packages:
self._metadata["materials_installed"] = all_packages["material"]
# TODO: ADD MATERIALS HERE ONCE MATERIALS PORTION OF TOOLBOX IS LIVE
self._models["materials_installed"].setMetadata(self._metadata["materials_installed"])
self._materials_installed_model.setMetadata(all_packages["material"])
self.metadataChanged.emit()
@pyqtSlot(str)
@ -479,7 +460,7 @@ class Toolbox(QObject, Extension):
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"]:
for package in self._server_response_data["packages"]:
if package["package_id"] == package_id:
remote_package = package
break
@ -491,11 +472,8 @@ class Toolbox(QObject, Extension):
def canUpdate(self, package_id: str) -> bool:
local_package = self._package_manager.getInstalledPackageInfo(package_id)
if local_package is None:
Logger.log("i", "Could not find package [%s] as installed in the package manager, fall back to check the old plugins",
package_id)
local_package = self.getOldPluginPackageMetadata(package_id)
if local_package is None:
Logger.log("i", "Could not find package [%s] in the old plugins", package_id)
return False
remote_package = self.getRemotePackage(package_id)
@ -545,8 +523,8 @@ class Toolbox(QObject, Extension):
@pyqtSlot(str, result = int)
def getNumberOfInstalledPackagesByAuthor(self, author_id: str) -> int:
count = 0
for package in self._metadata["materials_installed"]:
if package["author"]["author_id"] == author_id:
for package in self._materials_installed_model.items:
if package["author_id"] == author_id:
count += 1
return count
@ -554,7 +532,7 @@ class Toolbox(QObject, Extension):
@pyqtSlot(str, result = int)
def getTotalNumberOfMaterialPackagesByAuthor(self, author_id: str) -> int:
count = 0
for package in self._metadata["packages"]:
for package in self._server_response_data["packages"]:
if package["package_type"] == "material":
if package["author"]["author_id"] == author_id:
count += 1
@ -568,34 +546,30 @@ class Toolbox(QObject, Extension):
# Check for plugins that were installed with the old plugin browser
def isOldPlugin(self, plugin_id: str) -> bool:
if plugin_id in self._old_plugin_ids:
return True
return False
return plugin_id in self._old_plugin_ids
def getOldPluginPackageMetadata(self, plugin_id: str) -> Optional[Dict[str, Any]]:
return self._old_plugin_metadata.get(plugin_id)
def loadingComplete(self) -> bool:
def isLoadingComplete(self) -> bool:
populated = 0
for list in self._metadata.items():
if len(list) > 0:
for metadata_list in self._server_response_data.items():
if metadata_list:
populated += 1
if populated == len(self._metadata.items()):
return True
return False
return populated == len(self._server_response_data.items())
# Make API Calls
# --------------------------------------------------------------------------
def _makeRequestByType(self, type: str) -> None:
Logger.log("i", "Marketplace: Requesting %s metadata from server.", type)
request = QNetworkRequest(self._request_urls[type])
def _makeRequestByType(self, request_type: str) -> None:
Logger.log("i", "Requesting %s metadata from server.", request_type)
request = QNetworkRequest(self._request_urls[request_type])
request.setRawHeader(*self._request_header)
if self._network_manager:
self._network_manager.get(request)
@pyqtSlot(str)
def startDownload(self, url: str) -> None:
Logger.log("i", "Marketplace: Attempting to download & install package from %s.", url)
Logger.log("i", "Attempting to download & install package from %s.", url)
url = QUrl(url)
self._download_request = QNetworkRequest(url)
if hasattr(QNetworkRequest, "FollowRedirectsAttribute"):
@ -612,15 +586,15 @@ class Toolbox(QObject, Extension):
@pyqtSlot()
def cancelDownload(self) -> None:
Logger.log("i", "Marketplace: User cancelled the download of a package.")
Logger.log("i", "User cancelled the download of a package.")
self.resetDownload()
def resetDownload(self) -> None:
if self._download_reply:
try:
self._download_reply.downloadProgress.disconnect(self._onDownloadProgress)
except TypeError: #Raised when the method is not connected to the signal yet.
pass #Don't need to disconnect.
except TypeError: # Raised when the method is not connected to the signal yet.
pass # Don't need to disconnect.
self._download_reply.abort()
self._download_reply = None
self._download_request = None
@ -646,22 +620,8 @@ class Toolbox(QObject, Extension):
self.resetDownload()
return
# HACK: These request are not handled independently at this moment, but together from the "packages" call
do_not_handle = [
"materials_available",
"materials_showcase",
"materials_generic",
"plugins_available",
"plugins_showcase",
]
if reply.operation() == QNetworkAccessManager.GetOperation:
for type, url in self._request_urls.items():
# HACK: Do nothing because we'll handle these from the "packages" call
if type in do_not_handle:
continue
for response_type, url in self._request_urls.items():
if reply.url() == url:
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200:
try:
@ -674,38 +634,32 @@ class Toolbox(QObject, Extension):
return
# Create model and apply metadata:
if not self._models[type]:
Logger.log("e", "Could not find the %s model.", type)
if not self._models[response_type]:
Logger.log("e", "Could not find the %s model.", response_type)
break
self._metadata[type] = json_data["data"]
self._models[type].setMetadata(self._metadata[type])
self._server_response_data[response_type] = json_data["data"]
self._models[response_type].setMetadata(self._server_response_data[response_type])
# Do some auto filtering
# TODO: Make multiple API calls in the future to handle this
if type is "packages":
self._models[type].setFilter({"type": "plugin"})
self.buildMaterialsModels()
self.buildPluginsModels()
if type is "authors":
self._models[type].setFilter({"package_types": "material"})
if type is "materials_generic":
self._models[type].setFilter({"tags": "generic"})
if response_type is "packages":
self._models[response_type].setFilter({"type": "plugin"})
self.reBuildMaterialsModels()
self.reBuildPluginsModels()
elif response_type is "authors":
self._models[response_type].setFilter({"package_types": "material"})
self._models[response_type].setFilter({"tags": "generic"})
self.metadataChanged.emit()
if self.loadingComplete() is True:
if self.isLoadingComplete():
self.setViewPage("overview")
return
except json.decoder.JSONDecodeError:
Logger.log("w", "Marketplace: Received invalid JSON for %s.", type)
Logger.log("w", "Received invalid JSON for %s.", response_type)
break
else:
self.setViewPage("errored")
self.resetDownload()
return
else:
# Ignore any operation that is not a get operation
pass
@ -716,7 +670,13 @@ class Toolbox(QObject, Extension):
self.setDownloadProgress(new_progress)
if bytes_sent == bytes_total:
self.setIsDownloading(False)
cast(QNetworkReply, self._download_reply).downloadProgress.disconnect(self._onDownloadProgress)
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:
Logger.log("w", "Failed to download package. The following error was returned: %s", json.loads(bytes(self._download_reply.readAll()).decode("utf-8")))
return
# Must not delete the temporary file on Windows
self._temp_plugin_file = tempfile.NamedTemporaryFile(mode = "w+b", suffix = ".curapackage", delete = False)
file_path = self._temp_plugin_file.name
@ -726,10 +686,10 @@ class Toolbox(QObject, Extension):
self._onDownloadComplete(file_path)
def _onDownloadComplete(self, file_path: str) -> None:
Logger.log("i", "Marketplace: Download complete.")
Logger.log("i", "Download complete.")
package_info = self._package_manager.getPackageInfo(file_path)
if not package_info:
Logger.log("w", "Marketplace: Package file [%s] was not a valid CuraPackage.", file_path)
Logger.log("w", "Package file [%s] was not a valid CuraPackage.", file_path)
return
license_content = self._package_manager.getPackageLicense(file_path)
@ -738,7 +698,6 @@ class Toolbox(QObject, Extension):
return
self.install(file_path)
return
# Getter & Setters for Properties:
# --------------------------------------------------------------------------
@ -761,8 +720,9 @@ class Toolbox(QObject, Extension):
return self._is_downloading
def setActivePackage(self, package: Dict[str, Any]) -> None:
self._active_package = package
self.activePackageChanged.emit()
if self._active_package != package:
self._active_package = package
self.activePackageChanged.emit()
## The active package is the package that is currently being downloaded
@pyqtProperty(QObject, fset = setActivePackage, notify = activePackageChanged)
@ -770,16 +730,18 @@ class Toolbox(QObject, Extension):
return self._active_package
def setViewCategory(self, category: str = "plugin") -> None:
self._view_category = category
self.viewChanged.emit()
if self._view_category != category:
self._view_category = category
self.viewChanged.emit()
@pyqtProperty(str, fset = setViewCategory, notify = viewChanged)
def viewCategory(self) -> str:
return self._view_category
def setViewPage(self, page: str = "overview") -> None:
self._view_page = page
self.viewChanged.emit()
if self._view_page != page:
self._view_page = page
self.viewChanged.emit()
@pyqtProperty(str, fset = setViewPage, notify = viewChanged)
def viewPage(self) -> str:
@ -787,48 +749,48 @@ class Toolbox(QObject, Extension):
# Exposed Models:
# --------------------------------------------------------------------------
@pyqtProperty(QObject, notify = metadataChanged)
@pyqtProperty(QObject, constant=True)
def authorsModel(self) -> AuthorsModel:
return cast(AuthorsModel, self._models["authors"])
@pyqtProperty(QObject, notify = metadataChanged)
@pyqtProperty(QObject, constant=True)
def packagesModel(self) -> PackagesModel:
return cast(PackagesModel, self._models["packages"])
@pyqtProperty(QObject, notify = metadataChanged)
@pyqtProperty(QObject, constant=True)
def pluginsShowcaseModel(self) -> PackagesModel:
return cast(PackagesModel, self._models["plugins_showcase"])
return self._plugins_showcase_model
@pyqtProperty(QObject, notify = metadataChanged)
@pyqtProperty(QObject, constant=True)
def pluginsAvailableModel(self) -> PackagesModel:
return cast(PackagesModel, self._models["plugins_available"])
return self._plugins_available_model
@pyqtProperty(QObject, notify = metadataChanged)
@pyqtProperty(QObject, constant=True)
def pluginsInstalledModel(self) -> PackagesModel:
return cast(PackagesModel, self._models["plugins_installed"])
return self._plugins_installed_model
@pyqtProperty(QObject, notify = metadataChanged)
@pyqtProperty(QObject, constant=True)
def materialsShowcaseModel(self) -> AuthorsModel:
return cast(AuthorsModel, self._models["materials_showcase"])
return self._materials_showcase_model
@pyqtProperty(QObject, notify = metadataChanged)
@pyqtProperty(QObject, constant=True)
def materialsAvailableModel(self) -> AuthorsModel:
return cast(AuthorsModel, self._models["materials_available"])
return self._materials_available_model
@pyqtProperty(QObject, notify = metadataChanged)
@pyqtProperty(QObject, constant=True)
def materialsInstalledModel(self) -> PackagesModel:
return cast(PackagesModel, self._models["materials_installed"])
return self._materials_installed_model
@pyqtProperty(QObject, notify=metadataChanged)
@pyqtProperty(QObject, constant=True)
def materialsGenericModel(self) -> PackagesModel:
return cast(PackagesModel, self._models["materials_generic"])
return self._materials_generic_model
# Filter Models:
# --------------------------------------------------------------------------
@pyqtSlot(str, str, str)
def filterModelByProp(self, model_type: str, filter_type: str, parameter: str) -> None:
if not self._models[model_type]:
Logger.log("w", "Marketplace: Couldn't filter %s model because it doesn't exist.", model_type)
Logger.log("w", "Couldn't filter %s model because it doesn't exist.", model_type)
return
self._models[model_type].setFilter({filter_type: parameter})
self.filterChanged.emit()
@ -836,7 +798,7 @@ class Toolbox(QObject, Extension):
@pyqtSlot(str, "QVariantMap")
def setFilters(self, model_type: str, filter_dict: dict) -> None:
if not self._models[model_type]:
Logger.log("w", "Marketplace: Couldn't filter %s model because it doesn't exist.", model_type)
Logger.log("w", "Couldn't filter %s model because it doesn't exist.", model_type)
return
self._models[model_type].setFilter(filter_dict)
self.filterChanged.emit()
@ -844,21 +806,21 @@ class Toolbox(QObject, Extension):
@pyqtSlot(str)
def removeFilters(self, model_type: str) -> None:
if not self._models[model_type]:
Logger.log("w", "Marketplace: Couldn't remove filters on %s model because it doesn't exist.", model_type)
Logger.log("w", "Couldn't remove filters on %s model because it doesn't exist.", model_type)
return
self._models[model_type].setFilter({})
self.filterChanged.emit()
# HACK(S):
# --------------------------------------------------------------------------
def buildMaterialsModels(self) -> None:
self._metadata["materials_showcase"] = []
self._metadata["materials_available"] = []
self._metadata["materials_generic"] = []
def reBuildMaterialsModels(self) -> None:
materials_showcase_metadata = []
materials_available_metadata = []
materials_generic_metadata = []
processed_authors = [] # type: List[str]
processed_authors = [] # type: List[str]
for item in self._metadata["packages"]:
for item in self._server_response_data["packages"]:
if item["package_type"] == "material":
author = item["author"]
@ -867,30 +829,29 @@ class Toolbox(QObject, Extension):
# Generic materials to be in the same section
if "generic" in item["tags"]:
self._metadata["materials_generic"].append(item)
materials_generic_metadata.append(item)
else:
if "showcase" in item["tags"]:
self._metadata["materials_showcase"].append(author)
materials_showcase_metadata.append(author)
else:
self._metadata["materials_available"].append(author)
materials_available_metadata.append(author)
processed_authors.append(author["author_id"])
self._models["materials_showcase"].setMetadata(self._metadata["materials_showcase"])
self._models["materials_available"].setMetadata(self._metadata["materials_available"])
self._models["materials_generic"].setMetadata(self._metadata["materials_generic"])
self._materials_showcase_model.setMetadata(materials_showcase_metadata)
self._materials_available_model.setMetadata(materials_available_metadata)
self._materials_generic_model.setMetadata(materials_generic_metadata)
def buildPluginsModels(self) -> None:
self._metadata["plugins_showcase"] = []
self._metadata["plugins_available"] = []
def reBuildPluginsModels(self) -> None:
plugins_showcase_metadata = []
plugins_available_metadata = []
for item in self._metadata["packages"]:
for item in self._server_response_data["packages"]:
if item["package_type"] == "plugin":
if "showcase" in item["tags"]:
self._metadata["plugins_showcase"].append(item)
plugins_showcase_metadata.append(item)
else:
self._metadata["plugins_available"].append(item)
plugins_available_metadata.append(item)
self._models["plugins_showcase"].setMetadata(self._metadata["plugins_showcase"])
self._models["plugins_available"].setMetadata(self._metadata["plugins_available"])
self._plugins_showcase_model.setMetadata(plugins_showcase_metadata)
self._plugins_available_model.setMetadata(plugins_available_metadata)

View File

@ -11,6 +11,7 @@ import Cura 1.0 as Cura
UM.Dialog
{
id: base
title: catalog.i18nc("@title:window", "Save Project")
minimumWidth: 500 * screenScaleFactor
@ -49,7 +50,7 @@ UM.Dialog
UM.SettingDefinitionsModel
{
id: definitionsModel
containerId: Cura.MachineManager.activeDefinitionId
containerId: base.visible ? Cura.MachineManager.activeDefinitionId: ""
showAll: true
exclude: ["command_line_settings"]
showAncestors: true

View File

@ -16,10 +16,11 @@ Menu
Instantiator
{
model: Cura.ExtrudersModel { simpleNames: true }
model: Cura.MachineManager.activeMachine.extruderList
Menu
{
title: model.name
title: modelData.name
NozzleMenu { title: Cura.MachineManager.activeDefinitionVariantsName; visible: Cura.MachineManager.hasVariants; extruderIndex: index }
MaterialMenu { title: catalog.i18nc("@title:menu", "&Material"); visible: Cura.MachineManager.hasMaterials; extruderIndex: index }