Merge branch 'master' of github.com:Ultimaker/Cura into feature_intent

This commit is contained in:
Jaime van Kessel 2019-07-02 13:51:52 +02:00
commit 0cb83d2c92
41 changed files with 946 additions and 221 deletions

View file

@ -8,7 +8,6 @@ from UM.PluginRegistry import PluginRegistry
from UM.Logger import Logger
from UM.Settings.ContainerFormatError import ContainerFormatError
from UM.Settings.InstanceContainer import InstanceContainer # The new profile to make.
from cura.CuraApplication import CuraApplication #To get the current setting version.
from cura.ReaderWriters.ProfileReader import ProfileReader
import zipfile
@ -32,7 +31,7 @@ class CuraProfileReader(ProfileReader):
def read(self, file_name: str) -> List[Optional[InstanceContainer]]:
try:
with zipfile.ZipFile(file_name, "r") as archive:
results = [] #type: List[Optional[InstanceContainer]]
results = [] # type: List[Optional[InstanceContainer]]
for profile_id in archive.namelist():
with archive.open(profile_id) as f:
serialized = f.read()
@ -68,7 +67,7 @@ class CuraProfileReader(ProfileReader):
return []
version = int(parser["general"]["version"])
if InstanceContainer.Version != version or "metadata" not in parser or "setting_version" not in parser["metadata"] or parser["metadata"]["setting_version"] != str(CuraApplication.SettingVersion):
if InstanceContainer.Version != version:
name = parser["general"]["name"]
return self._upgradeProfileVersion(serialized, name, version)
else:
@ -107,9 +106,11 @@ class CuraProfileReader(ProfileReader):
if source_format in plugin["version_upgrade"] and plugin["version_upgrade"][source_format][1] == InstanceContainer.Version]
if not profile_convert_funcs:
Logger.log("w", "Unable to find an upgrade path for the profile [%s]", profile_id)
return []
filenames, outputs = profile_convert_funcs[0](serialized, profile_id)
if filenames is None and outputs is None:
Logger.log("w", "The conversion failed to return any usable data for [%s]", profile_id)
return []
return list(zip(outputs, filenames))

View file

@ -81,7 +81,7 @@ Item
enabled: visible && !(printJob.state == "pausing" || printJob.state == "resuming");
onClicked: {
if (printJob.state == "paused") {
printJob.setState("print");
printJob.setState("resume");
popUp.close();
return;
}

View file

@ -22,10 +22,6 @@ Item
// The print job which all other data is derived from
property var printJob: null
// If the printer is a cloud printer or not. Other items base their enabled state off of this boolean. In the future
// they might not need to though.
property bool cloudConnection: Cura.MachineManager.activeMachineIsUsingCloudConnection
width: parent.width
height: childrenRect.height
@ -217,7 +213,7 @@ Item
}
width: 32 * screenScaleFactor // TODO: Theme!
height: 32 * screenScaleFactor // TODO: Theme!
enabled: !cloudConnection
enabled: OutputDevice.supportsPrintJobActions
onClicked: enabled ? contextMenu.switchPopupState() : {}
visible:
{
@ -250,7 +246,7 @@ Item
MonitorInfoBlurb
{
id: contextMenuDisabledInfo
text: catalog.i18nc("@info", "These options are not available because you are monitoring a cloud printer.")
text: catalog.i18nc("@info", "Please update your printer's firmware to manage the queue remotely.")
target: contextMenuButton
}
}
}

View file

@ -172,8 +172,7 @@ Item
}
width: 36 * screenScaleFactor // TODO: Theme!
height: 36 * screenScaleFactor // TODO: Theme!
enabled: !cloudConnection
enabled: OutputDevice.supportsPrintJobActions
onClicked: enabled ? contextMenu.switchPopupState() : {}
visible:
{
@ -206,7 +205,7 @@ Item
MonitorInfoBlurb
{
id: contextMenuDisabledInfo
text: catalog.i18nc("@info", "These options are not available because you are monitoring a cloud printer.")
text: catalog.i18nc("@info", "Please update your printer's firmware to manage the queue remotely.")
target: contextMenuButton
}
@ -244,7 +243,6 @@ Item
}
}
// Divider
Rectangle
{

View file

@ -42,7 +42,6 @@ Item
}
height: 18 * screenScaleFactor // TODO: Theme!
width: childrenRect.width
visible: !cloudConnection
UM.RecolorImage
{
@ -65,7 +64,7 @@ Item
color: UM.Theme.getColor("monitor_text_link")
font: UM.Theme.getFont("medium") // 14pt, regular
linkColor: UM.Theme.getColor("monitor_text_link")
text: catalog.i18nc("@label link to connect manager", "Go to Cura Connect")
text: catalog.i18nc("@label link to connect manager", "Manage in browser")
renderType: Text.NativeRendering
}
}
@ -73,9 +72,7 @@ Item
MouseArea
{
anchors.fill: manageQueueLabel
enabled: !cloudConnection
hoverEnabled: !cloudConnection
onClicked: Cura.MachineManager.printerOutputDevices[0].openPrintJobControlPanel()
onClicked: OutputDevice.openPrintJobControlPanel()
onEntered:
{
manageQueueText.font.underline = true
@ -198,8 +195,7 @@ Item
color: UM.Theme.getColor("monitor_card_background")
border.color: UM.Theme.getColor("monitor_card_border")
radius: 2 * screenScaleFactor // TODO: Theme!
visible: printJobList.model.length == 0
visible: OutputDevice.printJobs.length == 0
Row
{
@ -249,14 +245,14 @@ Item
color: UM.Theme.getColor("monitor_text_link")
font: UM.Theme.getFont("medium") // 14pt, regular
linkColor: UM.Theme.getColor("monitor_text_link")
text: catalog.i18nc("@label link to connect manager", "View print history")
text: catalog.i18nc("@label link to connect manager", "Manage in browser")
renderType: Text.NativeRendering
}
MouseArea
{
anchors.fill: parent
hoverEnabled: true
onClicked: Cura.MachineManager.printerOutputDevices[0].openPrintJobControlPanel()
onClicked: OutputDevice.openPrintJobControlPanel()
onEntered:
{
viewPrintHistoryText.font.underline = true

View file

@ -96,6 +96,21 @@ class CloudApiClient:
reply = self._manager.post(self._createEmptyRequest(url), b"")
self._addCallback(reply, on_finished, CloudPrintResponse)
## Send a print job action to the cluster for the given print job.
# \param cluster_id: The ID of the cluster.
# \param cluster_job_id: The ID of the print job within the cluster.
# \param action: The name of the action to execute.
def doPrintJobAction(self, cluster_id: str, cluster_job_id: str, action: str, data: Optional[Dict[str, Any]] = None) -> None:
body = b""
if data:
try:
body = json.dumps({"data": data}).encode()
except JSONDecodeError as err:
Logger.log("w", "Could not encode body: %s", err)
return
url = "{}/clusters/{}/print_jobs/{}/action/{}".format(self.CLUSTER_API_ROOT, cluster_id, cluster_job_id, action)
self._manager.post(self._createEmptyRequest(url), body)
## We override _createEmptyRequest in order to add the user credentials.
# \param url: The URL to request
# \param content_type: The type of the body contents.

View file

@ -1,5 +1,6 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from cura.PrinterOutput.Models.PrintJobOutputModel import PrintJobOutputModel
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
from typing import TYPE_CHECKING
@ -13,10 +14,13 @@ class CloudOutputController(PrinterOutputController):
# The cloud connection only supports fetching the printer and queue status and adding a job to the queue.
# To let the UI know this we mark all features below as False.
self.can_pause = False
self.can_abort = False
self.can_pause = True
self.can_abort = True
self.can_pre_heat_bed = False
self.can_pre_heat_hotends = False
self.can_send_raw_gcode = False
self.can_control_manually = False
self.can_update_firmware = False
def setJobState(self, job: "PrintJobOutputModel", state: str):
self._output_device.setJobState(job.key, state)

View file

@ -6,6 +6,7 @@ from time import time
from typing import Dict, List, Optional, Set, cast
from PyQt5.QtCore import QObject, QUrl, pyqtProperty, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QDesktopServices
from UM import i18nCatalog
from UM.Backend.Backend import BackendState
@ -15,6 +16,7 @@ from UM.Message import Message
from UM.PluginRegistry import PluginRegistry
from UM.Qt.Duration import Duration, DurationFormat
from UM.Scene.SceneNode import SceneNode
from UM.Version import Version
from cura.CuraApplication import CuraApplication
from cura.PrinterOutput.NetworkedPrinterOutputDevice import AuthState, NetworkedPrinterOutputDevice
@ -48,6 +50,9 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
# The interval with which the remote clusters are checked
CHECK_CLUSTER_INTERVAL = 10.0 # seconds
# The minimum version of firmware that support print job actions over cloud.
PRINT_JOB_ACTIONS_MIN_VERSION = Version("5.3.0")
# Signal triggered when the print jobs in the queue were changed.
printJobsChanged = pyqtSignal()
@ -359,6 +364,13 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
).show()
self.writeFinished.emit()
## Whether the printer that this output device represents supports print job actions via the cloud.
@pyqtProperty(bool, notify = _clusterPrintersChanged)
def supportsPrintJobActions(self) -> bool:
version_number = self.printers[0].firmwareVersion.split(".")
firmware_version = Version([version_number[0], version_number[1], version_number[2]])
return firmware_version >= self.PRINT_JOB_ACTIONS_MIN_VERSION
## Gets the number of printers in the cluster.
# We use a minimum of 1 because cloud devices are always a cluster and printer discovery needs it.
@pyqtProperty(int, notify = _clusterPrintersChanged)
@ -399,6 +411,22 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
return [print_job for print_job in self._print_jobs if
print_job.assignedPrinter is not None and print_job.state != "queued"]
def setJobState(self, print_job_uuid: str, state: str) -> None:
self._api.doPrintJobAction(self._cluster.cluster_id, print_job_uuid, state)
@pyqtSlot(str)
def sendJobToTop(self, print_job_uuid: str) -> None:
self._api.doPrintJobAction(self._cluster.cluster_id, print_job_uuid, "move",
{"list": "queued", "to_position": 0})
@pyqtSlot(str)
def deleteJobFromQueue(self, print_job_uuid: str) -> None:
self._api.doPrintJobAction(self._cluster.cluster_id, print_job_uuid, "remove")
@pyqtSlot(str)
def forceSendJob(self, print_job_uuid: str) -> None:
self._api.doPrintJobAction(self._cluster.cluster_id, print_job_uuid, "force")
@pyqtSlot(int, result = str)
def formatDuration(self, seconds: int) -> str:
return Duration(seconds).getDisplayString(DurationFormat.Format.Short)
@ -411,6 +439,18 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
def getDateCompleted(self, time_remaining: int) -> str:
return formatDateCompleted(time_remaining)
@pyqtProperty(bool, notify=printJobsChanged)
def receivedPrintJobs(self) -> bool:
return bool(self._print_jobs)
@pyqtSlot()
def openPrintJobControlPanel(self) -> None:
QDesktopServices.openUrl(QUrl("https://mycloud.ultimaker.com"))
@pyqtSlot()
def openPrinterControlPanel(self) -> None:
QDesktopServices.openUrl(QUrl("https://mycloud.ultimaker.com"))
## TODO: The following methods are required by the monitor page QML, but are not actually available using cloud.
# TODO: We fake the methods here to not break the monitor page.
@ -422,30 +462,6 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
def setActiveCameraUrl(self, camera_url: "QUrl") -> None:
pass
@pyqtProperty(bool, notify = printJobsChanged)
def receivedPrintJobs(self) -> bool:
return bool(self._print_jobs)
@pyqtSlot()
def openPrintJobControlPanel(self) -> None:
pass
@pyqtSlot()
def openPrinterControlPanel(self) -> None:
pass
@pyqtSlot(str)
def sendJobToTop(self, print_job_uuid: str) -> None:
pass
@pyqtSlot(str)
def deleteJobFromQueue(self, print_job_uuid: str) -> None:
pass
@pyqtSlot(str)
def forceSendJob(self, print_job_uuid: str) -> None:
pass
@pyqtProperty("QVariantList", notify = _clusterPrintersChanged)
def connectedPrintersTypeCount(self) -> List[Dict[str, str]]:
return []

View file

@ -91,7 +91,6 @@ class CloudClusterPrintJobStatus(BaseCloudModel):
def createOutputModel(self, controller: CloudOutputController) -> UM3PrintJobOutputModel:
model = UM3PrintJobOutputModel(controller, self.uuid, self.name)
self.updateOutputModel(model)
return model
## Creates a new configuration model

View file

@ -140,6 +140,11 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
if self._printer_selection_dialog is not None:
self._printer_selection_dialog.show()
## Whether the printer that this output device represents supports print job actions via the local network.
@pyqtProperty(bool, constant=True)
def supportsPrintJobActions(self) -> bool:
return True
@pyqtProperty(int, constant=True)
def clusterSize(self) -> int:
return self._cluster_size

View file

@ -27,7 +27,7 @@ from UM.Version import Version
from . import ClusterUM3OutputDevice, LegacyUM3OutputDevice
from .Cloud.CloudOutputDeviceManager import CloudOutputDeviceManager
from .Cloud.CloudOutputDevice import CloudOutputDevice # typing
from .Cloud.CloudOutputDevice import CloudOutputDevice # typing
if TYPE_CHECKING:
from PyQt5.QtNetwork import QNetworkReply

View file

@ -72,9 +72,9 @@ class TestCloudOutputDevice(TestCase):
controller_fields = {
"_output_device": self.device,
"can_abort": False,
"can_abort": True,
"can_control_manually": False,
"can_pause": False,
"can_pause": True,
"can_pre_heat_bed": False,
"can_pre_heat_hotends": False,
"can_send_raw_gcode": False,

View file

@ -1196,6 +1196,13 @@ class XmlMaterialProfile(InstanceContainer):
"surface energy": "material_surface_energy",
"shrinkage percentage": "material_shrinkage_percentage",
"build volume temperature": "build_volume_temperature",
"anti ooze retracted position": "material_anti_ooze_retracted_position",
"anti ooze retract speed": "material_anti_ooze_retraction_speed",
"break preparation retracted position": "material_break_preparation_retracted_position",
"break preparation speed": "material_break_preparation_speed",
"break retracted position": "material_break_retracted_position",
"break speed": "material_break_speed",
"break temperature": "material_break_temperature"
}
__unmapped_settings = [
"hardware compatible",