diff --git a/cura/CameraImageProvider.py b/cura/CameraImageProvider.py
index ff5c51f24b..6a07f6b029 100644
--- a/cura/CameraImageProvider.py
+++ b/cura/CameraImageProvider.py
@@ -19,5 +19,11 @@ class CameraImageProvider(QQuickImageProvider):
return image, QSize(15, 15)
except AttributeError:
- pass
+ try:
+ image = output_device.activeCamera.getImage()
+
+ return image, QSize(15, 15)
+ except AttributeError:
+ pass
+
return QImage(), QSize(15, 15)
diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py
index 833f43e29c..dbc675a279 100755
--- a/cura/CuraApplication.py
+++ b/cura/CuraApplication.py
@@ -93,6 +93,7 @@ from . import CuraActions
from cura.Scene import ZOffsetDecorator
from . import CuraSplashScreen
from . import CameraImageProvider
+from . import PrintJobPreviewImageProvider
from . import MachineActionManager
from cura.TaskManagement.OnExitCallbackManager import OnExitCallbackManager
@@ -502,6 +503,7 @@ class CuraApplication(QtApplication):
def _onEngineCreated(self):
self._qml_engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
+ self._qml_engine.addImageProvider("print_job_preview", PrintJobPreviewImageProvider.PrintJobPreviewImageProvider())
@pyqtProperty(bool)
def needToShowUserAgreement(self):
diff --git a/cura/PrintJobPreviewImageProvider.py b/cura/PrintJobPreviewImageProvider.py
new file mode 100644
index 0000000000..a8df5aa273
--- /dev/null
+++ b/cura/PrintJobPreviewImageProvider.py
@@ -0,0 +1,27 @@
+from PyQt5.QtGui import QImage
+from PyQt5.QtQuick import QQuickImageProvider
+from PyQt5.QtCore import QSize
+
+from UM.Application import Application
+
+
+class PrintJobPreviewImageProvider(QQuickImageProvider):
+ def __init__(self):
+ super().__init__(QQuickImageProvider.Image)
+
+ ## Request a new image.
+ def requestImage(self, id: str, size: QSize) -> QImage:
+ # The id will have an uuid and an increment separated by a slash. As we don't care about the value of the
+ # increment, we need to strip that first.
+ uuid = id[id.find("/") + 1:]
+ for output_device in Application.getInstance().getOutputDeviceManager().getOutputDevices():
+ if not hasattr(output_device, "printJobs"):
+ continue
+
+ for print_job in output_device.printJobs:
+ if print_job.key == uuid:
+ if print_job.getPreviewImage():
+ return print_job.getPreviewImage(), QSize(15, 15)
+ else:
+ return QImage(), QSize(15, 15)
+ return QImage(), QSize(15,15)
\ No newline at end of file
diff --git a/cura/PrinterOutput/ConfigurationModel.py b/cura/PrinterOutput/ConfigurationModel.py
index c03d968b9e..a3d6afd01d 100644
--- a/cura/PrinterOutput/ConfigurationModel.py
+++ b/cura/PrinterOutput/ConfigurationModel.py
@@ -27,7 +27,13 @@ class ConfigurationModel(QObject):
return self._printer_type
def setExtruderConfigurations(self, extruder_configurations):
- self._extruder_configurations = extruder_configurations
+ if self._extruder_configurations != extruder_configurations:
+ self._extruder_configurations = extruder_configurations
+
+ for extruder_configuration in self._extruder_configurations:
+ extruder_configuration.extruderConfigurationChanged.connect(self.configurationChanged)
+
+ self.configurationChanged.emit()
@pyqtProperty("QVariantList", fset = setExtruderConfigurations, notify = configurationChanged)
def extruderConfigurations(self):
diff --git a/cura/PrinterOutput/ExtruderConfigurationModel.py b/cura/PrinterOutput/ExtruderConfigurationModel.py
index bc7f1a7c07..da0ad6b0b2 100644
--- a/cura/PrinterOutput/ExtruderConfigurationModel.py
+++ b/cura/PrinterOutput/ExtruderConfigurationModel.py
@@ -1,56 +1,67 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
+from typing import Optional
from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal
+from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
+
class ExtruderConfigurationModel(QObject):
extruderConfigurationChanged = pyqtSignal()
- def __init__(self):
+ def __init__(self, position: int = -1) -> None:
super().__init__()
- self._position = -1
- self._material = None
- self._hotend_id = None
+ self._position = position # type: int
+ self._material = None # type: Optional[MaterialOutputModel]
+ self._hotend_id = None # type: Optional[str]
- def setPosition(self, position):
+ def setPosition(self, position: int) -> None:
self._position = position
@pyqtProperty(int, fset = setPosition, notify = extruderConfigurationChanged)
- def position(self):
+ def position(self) -> int:
return self._position
- def setMaterial(self, material):
- self._material = material
+ def setMaterial(self, material: Optional[MaterialOutputModel]) -> None:
+ if self._hotend_id != material:
+ self._material = material
+ self.extruderConfigurationChanged.emit()
@pyqtProperty(QObject, fset = setMaterial, notify = extruderConfigurationChanged)
- def material(self):
+ def activeMaterial(self) -> Optional[MaterialOutputModel]:
return self._material
- def setHotendID(self, hotend_id):
- self._hotend_id = hotend_id
+ @pyqtProperty(QObject, fset=setMaterial, notify=extruderConfigurationChanged)
+ def material(self) -> Optional[MaterialOutputModel]:
+ return self._material
+
+ def setHotendID(self, hotend_id: Optional[str]) -> None:
+ if self._hotend_id != hotend_id:
+ self._hotend_id = hotend_id
+ self.extruderConfigurationChanged.emit()
@pyqtProperty(str, fset = setHotendID, notify = extruderConfigurationChanged)
- def hotendID(self):
+ def hotendID(self) -> Optional[str]:
return self._hotend_id
## This method is intended to indicate whether the configuration is valid or not.
# The method checks if the mandatory fields are or not set
# At this moment is always valid since we allow to have empty material and variants.
- def isValid(self):
+ def isValid(self) -> bool:
return True
- def __str__(self):
+ def __str__(self) -> str:
message_chunks = []
message_chunks.append("Position: " + str(self._position))
message_chunks.append("-")
- message_chunks.append("Material: " + self.material.type if self.material else "empty")
+ message_chunks.append("Material: " + self.activeMaterial.type if self.activeMaterial else "empty")
message_chunks.append("-")
message_chunks.append("HotendID: " + self.hotendID if self.hotendID else "empty")
return " ".join(message_chunks)
- def __eq__(self, other):
+ def __eq__(self, other) -> bool:
return hash(self) == hash(other)
# Calculating a hash function using the position of the extruder, the material GUID and the hotend id to check if is
diff --git a/cura/PrinterOutput/ExtruderOutputModel.py b/cura/PrinterOutput/ExtruderOutputModel.py
index 0726662c6c..30d53bbd85 100644
--- a/cura/PrinterOutput/ExtruderOutputModel.py
+++ b/cura/PrinterOutput/ExtruderOutputModel.py
@@ -12,64 +12,61 @@ if TYPE_CHECKING:
class ExtruderOutputModel(QObject):
- hotendIDChanged = pyqtSignal()
targetHotendTemperatureChanged = pyqtSignal()
hotendTemperatureChanged = pyqtSignal()
- activeMaterialChanged = pyqtSignal()
+
extruderConfigurationChanged = pyqtSignal()
isPreheatingChanged = pyqtSignal()
- def __init__(self, printer: "PrinterOutputModel", position, parent=None) -> None:
+ def __init__(self, printer: "PrinterOutputModel", position: int, parent=None) -> None:
super().__init__(parent)
- self._printer = printer
+ self._printer = printer # type: PrinterOutputModel
self._position = position
- self._target_hotend_temperature = 0 # type: float
- self._hotend_temperature = 0 # type: float
- self._hotend_id = ""
- self._active_material = None # type: Optional[MaterialOutputModel]
- self._extruder_configuration = ExtruderConfigurationModel()
- self._extruder_configuration.position = self._position
+ self._target_hotend_temperature = 0.0 # type: float
+ self._hotend_temperature = 0.0 # type: float
self._is_preheating = False
- def getPrinter(self):
+ # The extruder output model wraps the configuration model. This way we can use the same config model for jobs
+ # and extruders alike.
+ self._extruder_configuration = ExtruderConfigurationModel()
+ self._extruder_configuration.position = self._position
+ self._extruder_configuration.extruderConfigurationChanged.connect(self.extruderConfigurationChanged)
+
+ def getPrinter(self) -> "PrinterOutputModel":
return self._printer
- def getPosition(self):
+ def getPosition(self) -> int:
return self._position
# Does the printer support pre-heating the bed at all
@pyqtProperty(bool, constant=True)
- def canPreHeatHotends(self):
+ def canPreHeatHotends(self) -> bool:
if self._printer:
return self._printer.canPreHeatHotends
return False
- @pyqtProperty(QObject, notify = activeMaterialChanged)
+ @pyqtProperty(QObject, notify = extruderConfigurationChanged)
def activeMaterial(self) -> Optional["MaterialOutputModel"]:
- return self._active_material
+ return self._extruder_configuration.activeMaterial
- def updateActiveMaterial(self, material: Optional["MaterialOutputModel"]):
- if self._active_material != material:
- self._active_material = material
- self._extruder_configuration.material = self._active_material
- self.activeMaterialChanged.emit()
- self.extruderConfigurationChanged.emit()
+ def updateActiveMaterial(self, material: Optional["MaterialOutputModel"]) -> None:
+ self._extruder_configuration.setMaterial(material)
## Update the hotend temperature. This only changes it locally.
- def updateHotendTemperature(self, temperature: float):
+ def updateHotendTemperature(self, temperature: float) -> None:
if self._hotend_temperature != temperature:
self._hotend_temperature = temperature
self.hotendTemperatureChanged.emit()
- def updateTargetHotendTemperature(self, temperature: float):
+ def updateTargetHotendTemperature(self, temperature: float) -> None:
if self._target_hotend_temperature != temperature:
self._target_hotend_temperature = temperature
self.targetHotendTemperatureChanged.emit()
## Set the target hotend temperature. This ensures that it's actually sent to the remote.
@pyqtSlot(float)
- def setTargetHotendTemperature(self, temperature: float):
+ def setTargetHotendTemperature(self, temperature: float) -> None:
self._printer.getController().setTargetHotendTemperature(self._printer, self, temperature)
self.updateTargetHotendTemperature(temperature)
@@ -81,30 +78,26 @@ class ExtruderOutputModel(QObject):
def hotendTemperature(self) -> float:
return self._hotend_temperature
- @pyqtProperty(str, notify = hotendIDChanged)
+ @pyqtProperty(str, notify = extruderConfigurationChanged)
def hotendID(self) -> str:
- return self._hotend_id
+ return self._extruder_configuration.hotendID
- def updateHotendID(self, id: str):
- if self._hotend_id != id:
- self._hotend_id = id
- self._extruder_configuration.hotendID = self._hotend_id
- self.hotendIDChanged.emit()
- self.extruderConfigurationChanged.emit()
+ def updateHotendID(self, hotend_id: str) -> None:
+ self._extruder_configuration.setHotendID(hotend_id)
@pyqtProperty(QObject, notify = extruderConfigurationChanged)
- def extruderConfiguration(self):
+ def extruderConfiguration(self) -> Optional[ExtruderConfigurationModel]:
if self._extruder_configuration.isValid():
return self._extruder_configuration
return None
- def updateIsPreheating(self, pre_heating):
+ def updateIsPreheating(self, pre_heating: bool) -> None:
if self._is_preheating != pre_heating:
self._is_preheating = pre_heating
self.isPreheatingChanged.emit()
@pyqtProperty(bool, notify=isPreheatingChanged)
- def isPreheating(self):
+ def isPreheating(self) -> bool:
return self._is_preheating
## Pre-heats the extruder before printer.
@@ -113,9 +106,9 @@ class ExtruderOutputModel(QObject):
# Celsius.
# \param duration How long the bed should stay warm, in seconds.
@pyqtSlot(float, float)
- def preheatHotend(self, temperature, duration):
+ def preheatHotend(self, temperature: float, duration: float) -> None:
self._printer._controller.preheatHotend(self, temperature, duration)
@pyqtSlot()
- def cancelPreheatHotend(self):
- self._printer._controller.cancelPreheatHotend(self)
\ No newline at end of file
+ def cancelPreheatHotend(self) -> None:
+ self._printer._controller.cancelPreheatHotend(self)
diff --git a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py
index b7862251c9..94f86f19a3 100644
--- a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py
+++ b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py
@@ -188,40 +188,55 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
if reply in self._kept_alive_multiparts:
del self._kept_alive_multiparts[reply]
- def put(self, target: str, data: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
+ def _validateManager(self) -> None:
if self._manager is None:
self._createNetworkManager()
assert (self._manager is not None)
+
+ def put(self, target: str, data: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
+ self._validateManager()
request = self._createEmptyRequest(target)
self._last_request_time = time()
- reply = self._manager.put(request, data.encode())
- self._registerOnFinishedCallback(reply, on_finished)
+ if self._manager is not None:
+ reply = self._manager.put(request, data.encode())
+ self._registerOnFinishedCallback(reply, on_finished)
+ else:
+ Logger.log("e", "Could not find manager.")
+
+ def delete(self, target: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
+ self._validateManager()
+ request = self._createEmptyRequest(target)
+ self._last_request_time = time()
+ if self._manager is not None:
+ reply = self._manager.deleteResource(request)
+ self._registerOnFinishedCallback(reply, on_finished)
+ else:
+ Logger.log("e", "Could not find manager.")
def get(self, target: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
- if self._manager is None:
- self._createNetworkManager()
- assert (self._manager is not None)
+ self._validateManager()
request = self._createEmptyRequest(target)
self._last_request_time = time()
- reply = self._manager.get(request)
- self._registerOnFinishedCallback(reply, on_finished)
+ if self._manager is not None:
+ reply = self._manager.get(request)
+ self._registerOnFinishedCallback(reply, on_finished)
+ else:
+ Logger.log("e", "Could not find manager.")
def post(self, target: str, data: str, on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> None:
- if self._manager is None:
- self._createNetworkManager()
- assert (self._manager is not None)
+ self._validateManager()
request = self._createEmptyRequest(target)
self._last_request_time = time()
- reply = self._manager.post(request, data)
- if on_progress is not None:
- reply.uploadProgress.connect(on_progress)
- self._registerOnFinishedCallback(reply, on_finished)
+ if self._manager is not None:
+ reply = self._manager.post(request, data)
+ if on_progress is not None:
+ reply.uploadProgress.connect(on_progress)
+ self._registerOnFinishedCallback(reply, on_finished)
+ else:
+ Logger.log("e", "Could not find manager.")
def postFormWithParts(self, target: str, parts: List[QHttpPart], on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> QNetworkReply:
-
- if self._manager is None:
- self._createNetworkManager()
- assert (self._manager is not None)
+ self._validateManager()
request = self._createEmptyRequest(target, content_type=None)
multi_post_part = QHttpMultiPart(QHttpMultiPart.FormDataType)
for part in parts:
@@ -229,15 +244,18 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
self._last_request_time = time()
- reply = self._manager.post(request, multi_post_part)
+ if self._manager is not None:
+ reply = self._manager.post(request, multi_post_part)
- self._kept_alive_multiparts[reply] = multi_post_part
+ self._kept_alive_multiparts[reply] = multi_post_part
- if on_progress is not None:
- reply.uploadProgress.connect(on_progress)
- self._registerOnFinishedCallback(reply, on_finished)
+ if on_progress is not None:
+ reply.uploadProgress.connect(on_progress)
+ self._registerOnFinishedCallback(reply, on_finished)
- return reply
+ return reply
+ else:
+ Logger.log("e", "Could not find manager.")
def postForm(self, target: str, header_data: str, body_data: bytes, on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> None:
post_part = QHttpPart()
diff --git a/cura/PrinterOutput/PrintJobOutputModel.py b/cura/PrinterOutput/PrintJobOutputModel.py
index b77600f85c..7366b95f86 100644
--- a/cura/PrinterOutput/PrintJobOutputModel.py
+++ b/cura/PrinterOutput/PrintJobOutputModel.py
@@ -2,11 +2,15 @@
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot
-from typing import Optional, TYPE_CHECKING
+from typing import Optional, TYPE_CHECKING, List
+
+from PyQt5.QtCore import QUrl
+from PyQt5.QtGui import QImage
if TYPE_CHECKING:
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
+ from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
class PrintJobOutputModel(QObject):
@@ -17,6 +21,9 @@ class PrintJobOutputModel(QObject):
keyChanged = pyqtSignal()
assignedPrinterChanged = pyqtSignal()
ownerChanged = pyqtSignal()
+ configurationChanged = pyqtSignal()
+ previewImageChanged = pyqtSignal()
+ compatibleMachineFamiliesChanged = pyqtSignal()
def __init__(self, output_controller: "PrinterOutputController", key: str = "", name: str = "", parent=None) -> None:
super().__init__(parent)
@@ -29,6 +36,48 @@ class PrintJobOutputModel(QObject):
self._assigned_printer = None # type: Optional[PrinterOutputModel]
self._owner = "" # Who started/owns the print job?
+ self._configuration = None # type: Optional[ConfigurationModel]
+ self._compatible_machine_families = [] # type: List[str]
+ self._preview_image_id = 0
+
+ self._preview_image = None # type: Optional[QImage]
+
+ @pyqtProperty("QStringList", notify=compatibleMachineFamiliesChanged)
+ def compatibleMachineFamilies(self):
+ # Hack; Some versions of cluster will return a family more than once...
+ return set(self._compatible_machine_families)
+
+ def setCompatibleMachineFamilies(self, compatible_machine_families: List[str]) -> None:
+ if self._compatible_machine_families != compatible_machine_families:
+ self._compatible_machine_families = compatible_machine_families
+ self.compatibleMachineFamiliesChanged.emit()
+
+ @pyqtProperty(QUrl, notify=previewImageChanged)
+ def previewImageUrl(self):
+ self._preview_image_id += 1
+ # There is an image provider that is called "camera". In order to ensure that the image qml object, that
+ # requires a QUrl to function, updates correctly we add an increasing number. This causes to see the QUrl
+ # as new (instead of relying on cached version and thus forces an update.
+ temp = "image://print_job_preview/" + str(self._preview_image_id) + "/" + self._key
+ return QUrl(temp, QUrl.TolerantMode)
+
+ def getPreviewImage(self) -> Optional[QImage]:
+ return self._preview_image
+
+ def updatePreviewImage(self, preview_image: Optional[QImage]) -> None:
+ if self._preview_image != preview_image:
+ self._preview_image = preview_image
+ self.previewImageChanged.emit()
+
+ @pyqtProperty(QObject, notify=configurationChanged)
+ def configuration(self) -> Optional["ConfigurationModel"]:
+ return self._configuration
+
+ def updateConfiguration(self, configuration: Optional["ConfigurationModel"]) -> None:
+ if self._configuration != configuration:
+ self._configuration = configuration
+ self.configurationChanged.emit()
+
@pyqtProperty(str, notify=ownerChanged)
def owner(self):
return self._owner
diff --git a/cura/PrinterOutput/PrinterOutputModel.py b/cura/PrinterOutput/PrinterOutputModel.py
index f10d6bd75b..f009a33178 100644
--- a/cura/PrinterOutput/PrinterOutputModel.py
+++ b/cura/PrinterOutput/PrinterOutputModel.py
@@ -35,7 +35,7 @@ class PrinterOutputModel(QObject):
self._key = "" # Unique identifier
self._controller = output_controller
self._extruders = [ExtruderOutputModel(printer = self, position = i) for i in range(number_of_extruders)]
- self._printer_configuration = ConfigurationModel() # Indicates the current configuration setup in this printer
+ self._printer_configuration = ConfigurationModel() # Indicates the current configuration setup in this printer
self._head_position = Vector(0, 0, 0)
self._active_print_job = None # type: Optional[PrintJobOutputModel]
self._firmware_version = firmware_version
@@ -43,9 +43,9 @@ class PrinterOutputModel(QObject):
self._is_preheating = False
self._printer_type = ""
self._buildplate_name = None
- # Update the printer configuration every time any of the extruders changes its configuration
- for extruder in self._extruders:
- extruder.extruderConfigurationChanged.connect(self._updateExtruderConfiguration)
+
+ self._printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in
+ self._extruders]
self._camera = None
@@ -282,8 +282,4 @@ class PrinterOutputModel(QObject):
def printerConfiguration(self):
if self._printer_configuration.isValid():
return self._printer_configuration
- return None
-
- def _updateExtruderConfiguration(self):
- self._printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in self._extruders]
- self.configurationChanged.emit()
+ return None
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/ClusterControlItem.qml b/plugins/UM3NetworkPrinting/ClusterControlItem.qml
index 5cf550955c..be72d3c07a 100644
--- a/plugins/UM3NetworkPrinting/ClusterControlItem.qml
+++ b/plugins/UM3NetworkPrinting/ClusterControlItem.qml
@@ -1,21 +1,26 @@
-import QtQuick 2.2
+import QtQuick 2.3
import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.3
+import QtGraphicalEffects 1.0
+
+import QtQuick.Controls 2.0 as Controls2
import UM 1.3 as UM
import Cura 1.0 as Cura
+
Component
{
Rectangle
{
id: base
- property var manager: Cura.MachineManager.printerOutputDevices[0]
property var lineColor: "#DCDCDC" // TODO: Should be linked to theme.
- property var cornerRadius: 4 * screenScaleFactor // TODO: Should be linked to theme.
- visible: manager != null
+
+ property var cornerRadius: 4 * screenScaleFactor // TODO: Should be linked to theme.
+ visible: OutputDevice != null
anchors.fill: parent
- color: UM.Theme.getColor("viewport_background")
+ color: "white"
UM.I18nCatalog
{
@@ -25,217 +30,689 @@ Component
Label
{
- id: activePrintersLabel
+ id: printingLabel
font: UM.Theme.getFont("large")
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.topMargin: UM.Theme.getSize("default_margin").height
- anchors.top: parent.top
- anchors.left: parent.left
- anchors.leftMargin: UM.Theme.getSize("default_margin").width
- anchors.right:parent.right
- anchors.rightMargin: UM.Theme.getSize("default_margin").width
- text: Cura.MachineManager.printerOutputDevices[0].name
+ anchors
+ {
+ margins: 2 * UM.Theme.getSize("default_margin").width
+ leftMargin: 4 * UM.Theme.getSize("default_margin").width
+ top: parent.top
+ left: parent.left
+ right: parent.right
+ }
+
+ text: catalog.i18nc("@label", "Printing")
elide: Text.ElideRight
}
- Rectangle
+ Label
{
- id: printJobArea
- border.width: UM.Theme.getSize("default_lining").width
- border.color: lineColor
- anchors.top: activePrintersLabel.bottom
- anchors.topMargin: UM.Theme.getSize("default_margin").height
- anchors.left: parent.left
- anchors.leftMargin: UM.Theme.getSize("default_margin").width
- anchors.right: parent.right
- anchors.rightMargin:UM.Theme.getSize("default_margin").width
- radius: cornerRadius
- height: childrenRect.height
-
- Item
- {
- id: printJobTitleBar
- width: parent.width
- height: printJobTitleLabel.height + 2 * UM.Theme.getSize("default_margin").height
-
- Label
- {
- id: printJobTitleLabel
- anchors.left: parent.left
- anchors.leftMargin: UM.Theme.getSize("default_margin").width
- anchors.top: parent.top
- anchors.topMargin: UM.Theme.getSize("default_margin").height
- text: catalog.i18nc("@title", "Print jobs")
- font: UM.Theme.getFont("default")
- opacity: 0.75
- }
- Rectangle
- {
- anchors.bottom: parent.bottom
- height: UM.Theme.getSize("default_lining").width
- color: lineColor
- width: parent.width
- }
- }
-
- Column
- {
- id: printJobColumn
- anchors.top: printJobTitleBar.bottom
- anchors.topMargin: UM.Theme.getSize("default_margin").height
- anchors.left: parent.left
- anchors.leftMargin: UM.Theme.getSize("default_margin").width
- anchors.right: parent.right
- anchors.rightMargin: UM.Theme.getSize("default_margin").width
-
- Item
- {
- width: parent.width
- height: childrenRect.height
- opacity: 0.65
- Label
- {
- text: catalog.i18nc("@label", "Printing")
- font: UM.Theme.getFont("very_small")
-
- }
- Label
- {
- text: manager.activePrintJobs.length
- font: UM.Theme.getFont("small")
- anchors.right: parent.right
- }
- }
- Item
- {
- width: parent.width
- height: childrenRect.height
- opacity: 0.65
- Label
- {
- text: catalog.i18nc("@label", "Queued")
- font: UM.Theme.getFont("very_small")
- }
- Label
- {
- text: manager.queuedPrintJobs.length
- font: UM.Theme.getFont("small")
- anchors.right: parent.right
- }
- }
- }
- OpenPanelButton
- {
- anchors.top: printJobColumn.bottom
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.margins: UM.Theme.getSize("default_margin").height
- id: configButton
- onClicked: base.manager.openPrintJobControlPanel()
- text: catalog.i18nc("@action:button", "View print jobs")
- }
-
- Item
- {
- // spacer
- anchors.top: configButton.bottom
- width: UM.Theme.getSize("default_margin").width
- height: UM.Theme.getSize("default_margin").height
- }
+ id: managePrintersLabel
+ anchors.rightMargin: 4 * UM.Theme.getSize("default_margin").width
+ anchors.right: printerScrollView.right
+ anchors.bottom: printingLabel.bottom
+ text: catalog.i18nc("@label link to connect manager", "Manage printers")
+ font: UM.Theme.getFont("default")
+ color: UM.Theme.getColor("primary")
+ linkColor: UM.Theme.getColor("primary")
}
-
- Rectangle
+ MouseArea
{
- id: printersArea
- border.width: UM.Theme.getSize("default_lining").width
- border.color: lineColor
- anchors.top: printJobArea.bottom
- anchors.topMargin: UM.Theme.getSize("default_margin").height
- anchors.left: parent.left
- anchors.leftMargin: UM.Theme.getSize("default_margin").width
- anchors.right: parent.right
- anchors.rightMargin:UM.Theme.getSize("default_margin").width
- radius: cornerRadius
- height: childrenRect.height
+ anchors.fill: managePrintersLabel
+ hoverEnabled: true
+ onClicked: Cura.MachineManager.printerOutputDevices[0].openPrinterControlPanel()
+ onEntered: managePrintersLabel.font.underline = true
+ onExited: managePrintersLabel.font.underline = false
+ }
- Item
+ ScrollView
+ {
+ id: printerScrollView
+ anchors
{
- id: printersTitleBar
- width: parent.width
- height: printJobTitleLabel.height + 2 * UM.Theme.getSize("default_margin").height
-
- Label
- {
- id: printersTitleLabel
- anchors.left: parent.left
- anchors.leftMargin: UM.Theme.getSize("default_margin").width
- anchors.top: parent.top
- anchors.topMargin: UM.Theme.getSize("default_margin").height
- text: catalog.i18nc("@label:title", "Printers")
- font: UM.Theme.getFont("default")
- opacity: 0.75
- }
- Rectangle
- {
- anchors.bottom: parent.bottom
- height: UM.Theme.getSize("default_lining").width
- color: lineColor
- width: parent.width
- }
+ top: printingLabel.bottom
+ left: parent.left
+ right: parent.right
+ topMargin: UM.Theme.getSize("default_margin").height
+ bottom: parent.bottom
+ bottomMargin: UM.Theme.getSize("default_margin").height
}
- Column
- {
- id: printersColumn
- anchors.top: printersTitleBar.bottom
- anchors.topMargin: UM.Theme.getSize("default_margin").height
- anchors.left: parent.left
- anchors.leftMargin: UM.Theme.getSize("default_margin").width
- anchors.right: parent.right
- anchors.rightMargin: UM.Theme.getSize("default_margin").width
- Repeater
+ style: UM.Theme.styles.scrollview
+
+ ListView
+ {
+ anchors
{
- model: manager.connectedPrintersTypeCount
- Item
+ top: parent.top
+ bottom: parent.bottom
+ left: parent.left
+ right: parent.right
+ leftMargin: 2 * UM.Theme.getSize("default_margin").width
+ rightMargin: 2 * UM.Theme.getSize("default_margin").width
+ }
+ spacing: UM.Theme.getSize("default_margin").height -10
+ model: OutputDevice.printers
+
+ delegate: Item
+ {
+ width: parent.width
+ height: base.height + 2 * base.shadowRadius // To ensure that the shadow doesn't get cut off.
+ Rectangle
{
- width: parent.width
- height: childrenRect.height
- opacity: 0.65
- Label
+ width: parent.width - 2 * shadowRadius
+ height: childrenRect.height + UM.Theme.getSize("default_margin").height
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+ id: base
+ property var shadowRadius: 5
+ property var collapsed: true
+
+ layer.enabled: true
+ layer.effect: DropShadow
{
- text: modelData.machine_type
- font: UM.Theme.getFont("very_small")
+ radius: base.shadowRadius
+ verticalOffset: 2
+ color: "#3F000000" // 25% shadow
}
- Label
+ Item
{
- text: modelData.count
- font: UM.Theme.getFont("small")
- anchors.right: parent.right
+ id: printerInfo
+ height: machineIcon.height
+ anchors
+ {
+ top: parent.top
+ left: parent.left
+ right: parent.right
+ margins: UM.Theme.getSize("default_margin").width
+ }
+
+ MouseArea
+ {
+ anchors.fill: parent
+ onClicked: base.collapsed = !base.collapsed
+ }
+
+ Item
+ {
+ id: machineIcon
+ // Yeah, this is hardcoded now, but I can't think of a good way to fix this.
+ // The UI is going to get another update soon, so it's probably not worth the effort...
+ width: 58
+ height: 58
+ anchors.top: parent.top
+ anchors.leftMargin: UM.Theme.getSize("default_margin").width
+ anchors.left: parent.left
+
+ UM.RecolorImage
+ {
+ anchors.centerIn: parent
+ source:
+ {
+ switch(modelData.type)
+ {
+ case "Ultimaker 3":
+ return "UM3-icon.svg"
+ case "Ultimaker 3 Extended":
+ return "UM3x-icon.svg"
+ case "Ultimaker S5":
+ return "UMs5-icon.svg"
+ }
+ }
+ width: sourceSize.width
+ height: sourceSize.height
+
+ color:
+ {
+ if(modelData.state == "disabled")
+ {
+ return UM.Theme.getColor("setting_control_disabled")
+ }
+
+ if(modelData.activePrintJob != undefined)
+ {
+ return UM.Theme.getColor("primary")
+ }
+
+ return UM.Theme.getColor("setting_control_disabled")
+ }
+ }
+ }
+ Item
+ {
+ height: childrenRect.height
+ anchors
+ {
+ right: collapseIcon.left
+ rightMargin: UM.Theme.getSize("default_margin").width
+ left: machineIcon.right
+ leftMargin: UM.Theme.getSize("default_margin").width
+
+ verticalCenter: machineIcon.verticalCenter
+ }
+
+ Label
+ {
+ id: machineNameLabel
+ text: modelData.name
+ width: parent.width
+ elide: Text.ElideRight
+ font: UM.Theme.getFont("default_bold")
+ }
+
+ Label
+ {
+ id: activeJobLabel
+ text:
+ {
+ if (modelData.state == "disabled")
+ {
+ return catalog.i18nc("@label", "Not available")
+ } else if (modelData.state == "unreachable")
+ {
+ return catalog.i18nc("@label", "Unreachable")
+ }
+ if (modelData.activePrintJob != null)
+ {
+ return modelData.activePrintJob.name
+ }
+ return catalog.i18nc("@label", "Available")
+ }
+ anchors.top: machineNameLabel.bottom
+ width: parent.width
+ elide: Text.ElideRight
+ font: UM.Theme.getFont("default")
+ opacity: 0.6
+ }
+ }
+
+ UM.RecolorImage
+ {
+ id: collapseIcon
+ width: 15
+ height: 15
+ sourceSize.width: width
+ sourceSize.height: height
+ source: base.collapsed ? UM.Theme.getIcon("arrow_left") : UM.Theme.getIcon("arrow_bottom")
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.right: parent.right
+ anchors.rightMargin: UM.Theme.getSize("default_margin").width
+ color: "black"
+ }
+ }
+
+ Item
+ {
+ id: detailedInfo
+ property var printJob: modelData.activePrintJob
+ visible: height == childrenRect.height
+ anchors.top: printerInfo.bottom
+ width: parent.width
+ height: !base.collapsed ? childrenRect.height : 0
+ opacity: visible ? 1 : 0
+ Behavior on height { NumberAnimation { duration: 100 } }
+ Behavior on opacity { NumberAnimation { duration: 100 } }
+ Rectangle
+ {
+ id: topSpacer
+ color: UM.Theme.getColor("viewport_background")
+ height: 2
+ anchors
+ {
+ left: parent.left
+ right: parent.right
+ margins: UM.Theme.getSize("default_margin").width
+ top: parent.top
+ topMargin: UM.Theme.getSize("default_margin").width
+ }
+ }
+ PrinterFamilyPill
+ {
+ id: printerFamilyPill
+ color: UM.Theme.getColor("viewport_background")
+ anchors.top: topSpacer.bottom
+ anchors.topMargin: 2 * UM.Theme.getSize("default_margin").height
+ text: modelData.type
+ anchors.left: parent.left
+ anchors.leftMargin: UM.Theme.getSize("default_margin").width
+ padding: 3
+ }
+ Row
+ {
+ id: extrudersInfo
+ anchors.top: printerFamilyPill.bottom
+ anchors.topMargin: 2 * UM.Theme.getSize("default_margin").height
+ anchors.left: parent.left
+ anchors.leftMargin: 2 * UM.Theme.getSize("default_margin").width
+ anchors.right: parent.right
+ anchors.rightMargin: 2 * UM.Theme.getSize("default_margin").width
+ height: childrenRect.height
+ spacing: UM.Theme.getSize("default_margin").width
+
+ PrintCoreConfiguration
+ {
+ id: leftExtruderInfo
+ width: Math.round(parent.width / 2)
+ printCoreConfiguration: modelData.printerConfiguration.extruderConfigurations[0]
+ }
+
+ PrintCoreConfiguration
+ {
+ id: rightExtruderInfo
+ width: Math.round(parent.width / 2)
+ printCoreConfiguration: modelData.printerConfiguration.extruderConfigurations[1]
+ }
+ }
+
+ Rectangle
+ {
+ id: jobSpacer
+ color: UM.Theme.getColor("viewport_background")
+ height: 2
+ anchors
+ {
+ left: parent.left
+ right: parent.right
+ margins: UM.Theme.getSize("default_margin").width
+ top: extrudersInfo.bottom
+ topMargin: 2 * UM.Theme.getSize("default_margin").height
+ }
+ }
+
+ Item
+ {
+ id: jobInfo
+ property var showJobInfo: modelData.activePrintJob != null && modelData.activePrintJob.state != "queued"
+
+ anchors.top: jobSpacer.bottom
+ anchors.topMargin: 2 * UM.Theme.getSize("default_margin").height
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.margins: UM.Theme.getSize("default_margin").width
+ anchors.leftMargin: 2 * UM.Theme.getSize("default_margin").width
+ height: showJobInfo ? childrenRect.height + 2 * UM.Theme.getSize("default_margin").height: 0
+ visible: showJobInfo
+ Label
+ {
+ id: printJobName
+ text: modelData.activePrintJob != null ? modelData.activePrintJob.name : ""
+ font: UM.Theme.getFont("default_bold")
+ anchors.left: parent.left
+ anchors.right: contextButton.left
+ anchors.rightMargin: UM.Theme.getSize("default_margin").width
+ elide: Text.ElideRight
+ }
+ Label
+ {
+ id: ownerName
+ anchors.top: printJobName.bottom
+ text: modelData.activePrintJob != null ? modelData.activePrintJob.owner : ""
+ font: UM.Theme.getFont("default")
+ opacity: 0.6
+ width: parent.width
+ elide: Text.ElideRight
+ }
+
+ function switchPopupState()
+ {
+ if (popup.visible)
+ {
+ popup.close()
+ }
+ else
+ {
+ popup.open()
+ }
+ }
+
+ Controls2.Button
+ {
+ id: contextButton
+ text: "\u22EE" //Unicode; Three stacked points.
+ font.pixelSize: 25
+ width: 35
+ height: width
+ anchors
+ {
+ right: parent.right
+ top: parent.top
+ }
+ hoverEnabled: true
+
+ background: Rectangle
+ {
+ opacity: contextButton.down || contextButton.hovered ? 1 : 0
+ width: contextButton.width
+ height: contextButton.height
+ radius: 0.5 * width
+ color: UM.Theme.getColor("viewport_background")
+ }
+
+ onClicked: parent.switchPopupState()
+ }
+
+ Controls2.Popup
+ {
+ // TODO Change once updating to Qt5.10 - The 'opened' property is in 5.10 but the behavior is now implemented with the visible property
+ id: popup
+ clip: true
+ closePolicy: Controls2.Popup.CloseOnPressOutsideParent
+ x: parent.width - width
+ y: contextButton.height
+ width: 160
+ height: contentItem.height + 2 * padding
+ visible: false
+
+ transformOrigin: Controls2.Popup.Top
+ contentItem: Item
+ {
+ width: popup.width - 2 * popup.padding
+ height: childrenRect.height + 15
+ Controls2.Button
+ {
+ id: pauseButton
+ text: modelData.activePrintJob != null && modelData.activePrintJob.state == "paused" ? catalog.i18nc("@label", "Resume") : catalog.i18nc("@label", "Pause")
+ onClicked:
+ {
+ if(modelData.activePrintJob.state == "paused")
+ {
+ modelData.activePrintJob.setState("print")
+ }
+ else if(modelData.activePrintJob.state == "printing")
+ {
+ modelData.activePrintJob.setState("pause")
+ }
+ popup.close()
+ }
+ width: parent.width
+ enabled: modelData.activePrintJob != null && ["paused", "printing"].indexOf(modelData.activePrintJob.state) >= 0
+ anchors.top: parent.top
+ anchors.topMargin: 10
+ hoverEnabled: true
+ background: Rectangle
+ {
+ opacity: pauseButton.down || pauseButton.hovered ? 1 : 0
+ color: UM.Theme.getColor("viewport_background")
+ }
+ }
+
+ Controls2.Button
+ {
+ id: abortButton
+ text: catalog.i18nc("@label", "Abort")
+ onClicked:
+ {
+ modelData.activePrintJob.setState("abort")
+ popup.close()
+ }
+ width: parent.width
+ anchors.top: pauseButton.bottom
+ hoverEnabled: true
+ enabled: modelData.activePrintJob != null && ["paused", "printing", "pre_print"].indexOf(modelData.activePrintJob.state) >= 0
+ background: Rectangle
+ {
+ opacity: abortButton.down || abortButton.hovered ? 1 : 0
+ color: UM.Theme.getColor("viewport_background")
+ }
+ }
+ }
+
+ background: Item
+ {
+ width: popup.width
+ height: popup.height
+
+ DropShadow
+ {
+ anchors.fill: pointedRectangle
+ radius: 5
+ color: "#3F000000" // 25% shadow
+ source: pointedRectangle
+ transparentBorder: true
+ verticalOffset: 2
+ }
+
+ Item
+ {
+ id: pointedRectangle
+ width: parent.width -10
+ height: parent.height -10
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+
+ Rectangle
+ {
+ id: point
+ height: 13
+ width: 13
+ color: UM.Theme.getColor("setting_control")
+ transform: Rotation { angle: 45}
+ anchors.right: bloop.right
+ y: 1
+ }
+
+ Rectangle
+ {
+ id: bloop
+ color: UM.Theme.getColor("setting_control")
+ width: parent.width
+ anchors.top: parent.top
+ anchors.topMargin: 10
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: 5
+ }
+ }
+ }
+
+ exit: Transition
+ {
+ // This applies a default NumberAnimation to any changes a state change makes to x or y properties
+ NumberAnimation { property: "visible"; duration: 75; }
+ }
+ enter: Transition
+ {
+ // This applies a default NumberAnimation to any changes a state change makes to x or y properties
+ NumberAnimation { property: "visible"; duration: 75; }
+ }
+
+ onClosed: visible = false
+ onOpened: visible = true
+ }
+
+ Image
+ {
+ id: printJobPreview
+ source: modelData.activePrintJob != null ? modelData.activePrintJob.previewImageUrl : ""
+ anchors.top: ownerName.bottom
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: parent.width / 2
+ height: width
+ opacity:
+ {
+ if(modelData.activePrintJob == null)
+ {
+ return 1.0
+ }
+
+ switch(modelData.activePrintJob.state)
+ {
+ case "wait_cleanup":
+ case "wait_user_action":
+ case "paused":
+ return 0.5
+ default:
+ return 1.0
+ }
+ }
+
+
+ }
+
+ UM.RecolorImage
+ {
+ id: statusImage
+ anchors.centerIn: printJobPreview
+ source:
+ {
+ if(modelData.activePrintJob == null)
+ {
+ return ""
+ }
+ switch(modelData.activePrintJob.state)
+ {
+ case "paused":
+ return "paused-icon.svg"
+ case "wait_cleanup":
+ if(modelData.activePrintJob.timeElapsed < modelData.activePrintJob.timeTotal)
+ {
+ return "aborted-icon.svg"
+ }
+ return "approved-icon.svg"
+ case "wait_user_action":
+ return "aborted-icon.svg"
+ default:
+ return ""
+ }
+ }
+ visible: source != ""
+ width: 0.5 * printJobPreview.width
+ height: 0.5 * printJobPreview.height
+ sourceSize.width: width
+ sourceSize.height: height
+ color: "black"
+ }
+
+ Rectangle
+ {
+ id: showCameraIcon
+ width: 35 * screenScaleFactor
+ height: width
+ radius: 0.5 * width
+ anchors.left: parent.left
+ anchors.bottom: printJobPreview.bottom
+ color: UM.Theme.getColor("setting_control_border_highlight")
+ Image
+ {
+ width: parent.width
+ height: width
+ anchors.right: parent.right
+ anchors.rightMargin: parent.rightMargin
+ source: "camera-icon.svg"
+ }
+ MouseArea
+ {
+ anchors.fill:parent
+ onClicked:
+ {
+ OutputDevice.setActiveCamera(modelData.camera)
+ }
+ }
+ }
+ }
+ }
+
+ ProgressBar
+ {
+ property var progress:
+ {
+ if(modelData.activePrintJob == null)
+ {
+ return 0
+ }
+ var result = modelData.activePrintJob.timeElapsed / modelData.activePrintJob.timeTotal
+ if(result > 1.0)
+ {
+ result = 1.0
+ }
+ return result
+ }
+
+ id: jobProgressBar
+ width: parent.width
+ value: progress
+ anchors.top: detailedInfo.bottom
+ anchors.topMargin: UM.Theme.getSize("default_margin").height
+
+ visible: modelData.activePrintJob != null && modelData.activePrintJob != undefined
+
+ style: ProgressBarStyle
+ {
+ property var progressText:
+ {
+ if(modelData.activePrintJob == null)
+ {
+ return ""
+ }
+
+ switch(modelData.activePrintJob.state)
+ {
+ case "wait_cleanup":
+ if(modelData.activePrintJob.timeTotal > modelData.activePrintJob.timeElapsed)
+ {
+ return catalog.i18nc("@label:status", "Aborted")
+ }
+ return catalog.i18nc("@label:status", "Finished")
+ case "pre_print":
+ case "sent_to_printer":
+ return catalog.i18nc("@label:status", "Preparing")
+ case "aborted":
+ case "wait_user_action":
+ return catalog.i18nc("@label:status", "Aborted")
+ case "pausing":
+ return catalog.i18nc("@label:status", "Pausing")
+ case "paused":
+ return catalog.i18nc("@label:status", "Paused")
+ case "resuming":
+ return catalog.i18nc("@label:status", "Resuming")
+ case "queued":
+ return catalog.i18nc("@label:status", "Action required")
+ default:
+ OutputDevice.formatDuration(modelData.activePrintJob.timeTotal - modelData.activePrintJob.timeElapsed)
+ }
+ }
+
+ background: Rectangle
+ {
+ implicitWidth: 100
+ implicitHeight: visible ? 24 : 0
+ color: UM.Theme.getColor("viewport_background")
+ }
+
+ progress: Rectangle
+ {
+ color: UM.Theme.getColor("primary")
+ id: progressItem
+ function getTextOffset()
+ {
+ if(progressItem.width + progressLabel.width < control.width)
+ {
+ return progressItem.width + UM.Theme.getSize("default_margin").width
+ }
+ else
+ {
+ return progressItem.width - progressLabel.width - UM.Theme.getSize("default_margin").width
+ }
+ }
+
+ Label
+ {
+ id: progressLabel
+ anchors.left: parent.left
+ anchors.leftMargin: getTextOffset()
+ text: progressText
+ anchors.verticalCenter: parent.verticalCenter
+ color: progressItem.width + progressLabel.width < control.width ? "black" : "white"
+ width: contentWidth
+ font: UM.Theme.getFont("default")
+ }
+ }
+ }
}
}
}
}
- OpenPanelButton
- {
- anchors.top: printersColumn.bottom
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.margins: UM.Theme.getSize("default_margin").height
- id: printerConfigButton
- onClicked: base.manager.openPrinterControlPanel()
-
- text: catalog.i18nc("@action:button", "View printers")
- }
-
- Item
- {
- // spacer
- anchors.top: printerConfigButton.bottom
- width: UM.Theme.getSize("default_margin").width
- height: UM.Theme.getSize("default_margin").height
- }
}
}
}
diff --git a/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml b/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml
index 0e86d55de8..71b598d05c 100644
--- a/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml
+++ b/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml
@@ -25,93 +25,83 @@ Component
Label
{
- id: activePrintersLabel
- font: UM.Theme.getFont("large")
-
- anchors {
- top: parent.top
- topMargin: UM.Theme.getSize("default_margin").height * 2 // a bit more spacing to give it some breathing room
- horizontalCenter: parent.horizontalCenter
- }
-
- text: OutputDevice.printers.length == 0 ? catalog.i18nc("@label: arg 1 is group name", "%1 is not set up to host a group of connected Ultimaker 3 printers").arg(Cura.MachineManager.printerOutputDevices[0].name) : ""
-
- visible: OutputDevice.printers.length == 0
+ id: manageQueueLabel
+ anchors.rightMargin: 4 * UM.Theme.getSize("default_margin").width
+ anchors.right: queuedPrintJobs.right
+ anchors.bottom: queuedLabel.bottom
+ text: catalog.i18nc("@label link to connect manager", "Manage queue")
+ font: UM.Theme.getFont("default")
+ color: UM.Theme.getColor("primary")
+ linkColor: UM.Theme.getColor("primary")
}
- Item
+ MouseArea
{
- anchors.topMargin: UM.Theme.getSize("default_margin").height
+ anchors.fill: manageQueueLabel
+ hoverEnabled: true
+ onClicked: Cura.MachineManager.printerOutputDevices[0].openPrintJobControlPanel()
+ onEntered: manageQueueLabel.font.underline = true
+ onExited: manageQueueLabel.font.underline = false
+ }
+
+ Label
+ {
+ id: queuedLabel
+ anchors.left: queuedPrintJobs.left
anchors.top: parent.top
- anchors.horizontalCenter: parent.horizontalCenter
-
- width: Math.min(800 * screenScaleFactor, maximumWidth)
- height: children.height
- visible: OutputDevice.printers.length != 0
-
- Label
- {
- id: addRemovePrintersLabel
- anchors.right: parent.right
- text: catalog.i18nc("@label link to connect manager", "Add/Remove printers")
- font: UM.Theme.getFont("default")
- color: UM.Theme.getColor("text")
- linkColor: UM.Theme.getColor("text_link")
- }
-
- MouseArea
- {
- anchors.fill: addRemovePrintersLabel
- hoverEnabled: true
- onClicked: Cura.MachineManager.printerOutputDevices[0].openPrinterControlPanel()
- onEntered: addRemovePrintersLabel.font.underline = true
- onExited: addRemovePrintersLabel.font.underline = false
- }
+ anchors.topMargin: 2 * UM.Theme.getSize("default_margin").height
+ anchors.leftMargin: 3 * UM.Theme.getSize("default_margin").width
+ text: catalog.i18nc("@label", "Queued")
+ font: UM.Theme.getFont("large")
+ color: UM.Theme.getColor("text")
}
ScrollView
{
- id: printerScrollView
- anchors.margins: UM.Theme.getSize("default_margin").width
- anchors.top: activePrintersLabel.bottom
- anchors.bottom: parent.bottom
- anchors.left: parent.left
- anchors.leftMargin: UM.Theme.getSize("default_lining").width // To ensure border can be drawn.
- anchors.rightMargin: UM.Theme.getSize("default_lining").width
- anchors.right: parent.right
+ id: queuedPrintJobs
+ anchors
+ {
+ top: queuedLabel.bottom
+ topMargin: UM.Theme.getSize("default_margin").height
+ horizontalCenter: parent.horizontalCenter
+ bottomMargin: 0
+ bottom: parent.bottom
+ }
+ style: UM.Theme.styles.scrollview
+ width: Math.min(800 * screenScaleFactor, maximumWidth)
ListView
{
anchors.fill: parent
- spacing: -UM.Theme.getSize("default_lining").height
+ //anchors.margins: UM.Theme.getSize("default_margin").height
+ spacing: UM.Theme.getSize("default_margin").height - 10 // 2x the shadow radius
- model: OutputDevice.printers
+ model: OutputDevice.queuedPrintJobs
- delegate: PrinterInfoBlock
+ delegate: PrintJobInfoBlock
{
- printer: modelData
- width: Math.min(800 * screenScaleFactor, maximumWidth)
- height: 125 * screenScaleFactor
-
- // Add a 1 pix margin, as the border is sometimes cut off otherwise.
- anchors.horizontalCenter: parent.horizontalCenter
+ printJob: modelData
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.rightMargin: UM.Theme.getSize("default_margin").height
+ anchors.leftMargin: UM.Theme.getSize("default_margin").height
+ height: 175 * screenScaleFactor
}
}
}
PrinterVideoStream
{
- visible: OutputDevice.activePrinter != null
- anchors.fill:parent
+ visible: OutputDevice.activeCamera != null
+ anchors.fill: parent
+ camera: OutputDevice.activeCamera
}
onVisibleChanged:
{
- if (!monitorFrame.visible)
+ if (monitorFrame != null && !monitorFrame.visible)
{
- // After switching the Tab ensure that active printer is Null, the video stream image
- // might be active
- OutputDevice.setActivePrinter(null)
+ OutputDevice.setActiveCamera(null)
}
}
}
diff --git a/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py b/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py
index e85961f619..8345de049c 100644
--- a/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py
+++ b/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py
@@ -4,19 +4,21 @@
from typing import Any, cast, Optional, Set, Tuple, Union
from UM.FileHandler.FileHandler import FileHandler
-from UM.FileHandler.FileWriter import FileWriter #To choose based on the output file mode (text vs. binary).
-from UM.FileHandler.WriteFileJob import WriteFileJob #To call the file writer asynchronously.
+from UM.FileHandler.FileWriter import FileWriter # To choose based on the output file mode (text vs. binary).
+from UM.FileHandler.WriteFileJob import WriteFileJob # To call the file writer asynchronously.
from UM.Logger import Logger
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.i18n import i18nCatalog
-from UM.Mesh.MeshWriter import MeshWriter # For typing
+
from UM.Message import Message
from UM.Qt.Duration import Duration, DurationFormat
-from UM.OutputDevice import OutputDeviceError #To show that something went wrong when writing.
-from UM.Scene.SceneNode import SceneNode #For typing.
-from UM.Version import Version #To check against firmware versions for support.
+from UM.OutputDevice import OutputDeviceError # To show that something went wrong when writing.
+from UM.Scene.SceneNode import SceneNode # For typing.
+from UM.Version import Version # To check against firmware versions for support.
from cura.CuraApplication import CuraApplication
+from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
+from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel
from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
@@ -27,14 +29,14 @@ from .ClusterUM3PrinterOutputController import ClusterUM3PrinterOutputController
from .SendMaterialJob import SendMaterialJob
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
-from PyQt5.QtGui import QDesktopServices
+from PyQt5.QtGui import QDesktopServices, QImage
from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal, pyqtProperty, QObject
from time import time
from datetime import datetime
-from typing import Optional, Dict, List, Set
+from typing import Optional, Dict, List
-import io #To create the correct buffers for sending data to the printer.
+import io # To create the correct buffers for sending data to the printer.
import json
import os
@@ -44,6 +46,7 @@ i18n_catalog = i18nCatalog("cura")
class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
printJobsChanged = pyqtSignal()
activePrinterChanged = pyqtSignal()
+ activeCameraChanged = pyqtSignal()
# This is a bit of a hack, as the notify can only use signals that are defined by the class that they are in.
# Inheritance doesn't seem to work. Tying them together does work, but i'm open for better suggestions.
@@ -65,18 +68,18 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
# See comments about this hack with the clusterPrintersChanged signal
self.printersChanged.connect(self.clusterPrintersChanged)
- self._accepts_commands = True #type: bool
+ self._accepts_commands = True # type: bool
# Cluster does not have authentication, so default to authenticated
self._authentication_state = AuthState.Authenticated
- self._error_message = None #type: Optional[Message]
- self._write_job_progress_message = None #type: Optional[Message]
- self._progress_message = None #type: Optional[Message]
+ self._error_message = None # type: Optional[Message]
+ self._write_job_progress_message = None # type: Optional[Message]
+ self._progress_message = None # type: Optional[Message]
self._active_printer = None # type: Optional[PrinterOutputModel]
- self._printer_selection_dialog = None #type: QObject
+ self._printer_selection_dialog = None # type: QObject
self.setPriority(3) # Make sure the output device gets selected above local file output
self.setName(self._id)
@@ -87,32 +90,35 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
self._printer_uuid_to_unique_name_mapping = {} # type: Dict[str, str]
- self._finished_jobs = [] # type: List[PrintJobOutputModel]
+ self._finished_jobs = [] # type: List[PrintJobOutputModel]
- self._cluster_size = int(properties.get(b"cluster_size", 0))
+ self._cluster_size = int(properties.get(b"cluster_size", 0)) # type: int
- self._latest_reply_handler = None #type: Optional[QNetworkReply]
+ self._latest_reply_handler = None # type: Optional[QNetworkReply]
+ self._sending_job = None
+
+ self._active_camera = None # type: Optional[NetworkCamera]
def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs: str) -> None:
self.writeStarted.emit(self)
self.sendMaterialProfiles()
- #Formats supported by this application (file types that we can actually write).
+ # Formats supported by this application (file types that we can actually write).
if file_handler:
file_formats = file_handler.getSupportedFileTypesWrite()
else:
file_formats = CuraApplication.getInstance().getMeshFileHandler().getSupportedFileTypesWrite()
global_stack = CuraApplication.getInstance().getGlobalContainerStack()
- #Create a list from the supported file formats string.
+ # Create a list from the supported file formats string.
if not global_stack:
Logger.log("e", "Missing global stack!")
return
machine_file_formats = global_stack.getMetaDataEntry("file_formats").split(";")
machine_file_formats = [file_type.strip() for file_type in machine_file_formats]
- #Exception for UM3 firmware version >=4.4: UFP is now supported and should be the preferred file format.
+ # Exception for UM3 firmware version >=4.4: UFP is now supported and should be the preferred file format.
if "application/x-ufp" not in machine_file_formats and Version(self.firmwareVersion) >= Version("4.4"):
machine_file_formats = ["application/x-ufp"] + machine_file_formats
@@ -125,7 +131,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
raise OutputDeviceError.WriteRequestFailedError(i18n_catalog.i18nc("@info:status", "There are no file formats available to write with!"))
preferred_format = file_formats[0]
- #Just take the first file format available.
+ # Just take the first file format available.
if file_handler is not None:
writer = file_handler.getWriterByMimeType(cast(str, preferred_format["mime_type"]))
else:
@@ -135,19 +141,20 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
Logger.log("e", "Unexpected error when trying to get the FileWriter")
return
- #This function pauses with the yield, waiting on instructions on which printer it needs to print with.
+ # This function pauses with the yield, waiting on instructions on which printer it needs to print with.
if not writer:
Logger.log("e", "Missing file or mesh writer!")
return
self._sending_job = self._sendPrintJob(writer, preferred_format, nodes)
- self._sending_job.send(None) #Start the generator.
+ if self._sending_job is not None:
+ self._sending_job.send(None) # Start the generator.
- if len(self._printers) > 1: #We need to ask the user.
- self._spawnPrinterSelectionDialog()
- is_job_sent = True
- else: #Just immediately continue.
- self._sending_job.send("") #No specifically selected printer.
- is_job_sent = self._sending_job.send(None)
+ if len(self._printers) > 1: # We need to ask the user.
+ self._spawnPrinterSelectionDialog()
+ is_job_sent = True
+ else: # Just immediately continue.
+ self._sending_job.send("") # No specifically selected printer.
+ is_job_sent = self._sending_job.send(None)
def _spawnPrinterSelectionDialog(self):
if self._printer_selection_dialog is None:
@@ -157,7 +164,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
self._printer_selection_dialog.show()
@pyqtProperty(int, constant=True)
- def clusterSize(self):
+ def clusterSize(self) -> int:
return self._cluster_size
## Allows the user to choose a printer to print with from the printer
@@ -165,7 +172,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
# \param target_printer The name of the printer to target.
@pyqtSlot(str)
def selectPrinter(self, target_printer: str = "") -> None:
- self._sending_job.send(target_printer)
+ if self._sending_job is not None:
+ self._sending_job.send(target_printer)
@pyqtSlot()
def cancelPrintSelection(self) -> None:
@@ -214,8 +222,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
job.start()
- yield True #Return that we had success!
- yield #To prevent having to catch the StopIteration exception.
+ yield True # Return that we had success!
+ yield # To prevent having to catch the StopIteration exception.
def _sendPrintJobWaitOnWriteJobFinished(self, job: WriteFileJob) -> None:
if self._write_job_progress_message:
@@ -240,7 +248,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
file_name = CuraApplication.getInstance().getPrintInformation().jobName + "." + preferred_format["extension"]
- output = stream.getvalue() #Either str or bytes depending on the output mode.
+ output = stream.getvalue() # Either str or bytes depending on the output mode.
if isinstance(stream, io.StringIO):
output = cast(str, output).encode("utf-8")
output = cast(bytes, output)
@@ -253,6 +261,10 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
def activePrinter(self) -> Optional[PrinterOutputModel]:
return self._active_printer
+ @pyqtProperty(QObject, notify=activeCameraChanged)
+ def activeCamera(self) -> Optional[NetworkCamera]:
+ return self._active_camera
+
@pyqtSlot(QObject)
def setActivePrinter(self, printer: Optional[PrinterOutputModel]) -> None:
if self._active_printer != printer:
@@ -261,6 +273,19 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
self._active_printer = printer
self.activePrinterChanged.emit()
+ @pyqtSlot(QObject)
+ def setActiveCamera(self, camera: Optional[NetworkCamera]) -> None:
+ if self._active_camera != camera:
+ if self._active_camera:
+ self._active_camera.stop()
+
+ self._active_camera = camera
+
+ if self._active_camera:
+ self._active_camera.start()
+
+ self.activeCameraChanged.emit()
+
def _onPostPrintJobFinished(self, reply: QNetworkReply) -> None:
if self._progress_message:
self._progress_message.hide()
@@ -279,8 +304,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
# If successfully sent:
if bytes_sent == bytes_total:
- # Show a confirmation to the user so they know the job was sucessful and provide the option to switch to the
- # monitor tab.
+ # Show a confirmation to the user so they know the job was sucessful and provide the option to switch to
+ # the monitor tab.
self._success_message = Message(
i18n_catalog.i18nc("@info:status", "Print job was successfully sent to the printer."),
lifetime=5, dismissable=True,
@@ -329,7 +354,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
@pyqtProperty("QVariantList", notify = printJobsChanged)
def queuedPrintJobs(self) -> List[PrintJobOutputModel]:
- return [print_job for print_job in self._print_jobs if print_job.state == "queued"]
+ return [print_job for print_job in self._print_jobs if print_job.state == "queued" or print_job.state == "error"]
@pyqtProperty("QVariantList", notify = printJobsChanged)
def activePrintJobs(self) -> List[PrintJobOutputModel]:
@@ -348,6 +373,10 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
result.append({"machine_type": machine_type, "count": str(printer_count[machine_type])})
return result
+ @pyqtProperty("QVariantList", notify=clusterPrintersChanged)
+ def printers(self):
+ return self._printers
+
@pyqtSlot(int, result = str)
def formatDuration(self, seconds: int) -> str:
return Duration(seconds).getDisplayString(DurationFormat.Format.Short)
@@ -364,6 +393,19 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
datetime_completed = datetime.fromtimestamp(current_time + time_remaining)
return (datetime_completed.strftime("%a %b ") + "{day}".format(day=datetime_completed.day)).upper()
+ @pyqtSlot(str)
+ def sendJobToTop(self, print_job_uuid: str) -> None:
+ # This function is part of the output device (and not of the printjob output model) as this type of operation
+ # is a modification of the cluster queue and not of the actual job.
+ data = "{\"to_position\": 0}"
+ self.put("print_jobs/{uuid}/move_to_position".format(uuid = print_job_uuid), data, on_finished=None)
+
+ @pyqtSlot(str)
+ def deleteJobFromQueue(self, print_job_uuid: str) -> None:
+ # This function is part of the output device (and not of the printjob output model) as this type of operation
+ # is a modification of the cluster queue and not of the actual job.
+ self.delete("print_jobs/{uuid}".format(uuid = print_job_uuid), on_finished=None)
+
def _printJobStateChanged(self) -> None:
username = self._getUserName()
@@ -392,11 +434,26 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
super().connect()
self.sendMaterialProfiles()
+ def _onGetPreviewImageFinished(self, reply: QNetworkReply) -> None:
+ reply_url = reply.url().toString()
+
+ uuid = reply_url[reply_url.find("print_jobs/")+len("print_jobs/"):reply_url.rfind("/preview_image")]
+
+ print_job = findByKey(self._print_jobs, uuid)
+ if print_job:
+ image = QImage()
+ image.loadFromData(reply.readAll())
+ print_job.updatePreviewImage(image)
+
def _update(self) -> None:
super()._update()
self.get("printers/", on_finished = self._onGetPrintersDataFinished)
self.get("print_jobs/", on_finished = self._onGetPrintJobsFinished)
+ for print_job in self._print_jobs:
+ if print_job.getPreviewImage() is None:
+ self.get("print_jobs/{uuid}/preview_image".format(uuid=print_job.key), on_finished=self._onGetPreviewImageFinished)
+
def _onGetPrintJobsFinished(self, reply: QNetworkReply) -> None:
if not checkValidGetReply(reply):
return
@@ -407,16 +464,19 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
print_jobs_seen = []
job_list_changed = False
- for print_job_data in result:
+ for idx, print_job_data in enumerate(result):
print_job = findByKey(self._print_jobs, print_job_data["uuid"])
-
if print_job is None:
print_job = self._createPrintJobModel(print_job_data)
job_list_changed = True
+ elif not job_list_changed:
+ # Check if the order of the jobs has changed since the last check
+ if self._print_jobs.index(print_job) != idx:
+ job_list_changed = True
self._updatePrintJob(print_job, print_job_data)
- if print_job.state != "queued": # Print job should be assigned to a printer.
+ if print_job.state != "queued" and print_job.state != "error": # Print job should be assigned to a printer.
if print_job.state in ["failed", "finished", "aborted", "none"]:
# Print job was already completed, so don't attach it to a printer.
printer = None
@@ -437,6 +497,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
job_list_changed = job_list_changed or self._removeJob(removed_job)
if job_list_changed:
+ # Override the old list with the new list (either because jobs were removed / added or order changed)
+ self._print_jobs = print_jobs_seen
self.printJobsChanged.emit() # Do a single emit for all print job changes.
def _onGetPrintersDataFinished(self, reply: QNetworkReply) -> None:
@@ -478,16 +540,59 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
def _createPrintJobModel(self, data: Dict[str, Any]) -> PrintJobOutputModel:
print_job = PrintJobOutputModel(output_controller=ClusterUM3PrinterOutputController(self),
key=data["uuid"], name= data["name"])
+
+ configuration = ConfigurationModel()
+ extruders = [ExtruderConfigurationModel(position = idx) for idx in range(0, self._number_of_extruders)]
+ for index in range(0, self._number_of_extruders):
+ try:
+ extruder_data = data["configuration"][index]
+ except IndexError:
+ continue
+ extruder = extruders[int(data["configuration"][index]["extruder_index"])]
+ extruder.setHotendID(extruder_data.get("print_core_id", ""))
+ extruder.setMaterial(self._createMaterialOutputModel(extruder_data.get("material", {})))
+
+ configuration.setExtruderConfigurations(extruders)
+ print_job.updateConfiguration(configuration)
+ print_job.setCompatibleMachineFamilies(data.get("compatible_machine_families", []))
print_job.stateChanged.connect(self._printJobStateChanged)
- self._print_jobs.append(print_job)
return print_job
def _updatePrintJob(self, print_job: PrintJobOutputModel, data: Dict[str, Any]) -> None:
print_job.updateTimeTotal(data["time_total"])
print_job.updateTimeElapsed(data["time_elapsed"])
- print_job.updateState(data["status"])
+ impediments_to_printing = data.get("impediments_to_printing", [])
print_job.updateOwner(data["owner"])
+ status_set_by_impediment = False
+ for impediment in impediments_to_printing:
+ if impediment["severity"] == "UNFIXABLE":
+ status_set_by_impediment = True
+ print_job.updateState("error")
+ break
+
+ if not status_set_by_impediment:
+ print_job.updateState(data["status"])
+
+
+ def _createMaterialOutputModel(self, material_data) -> MaterialOutputModel:
+ containers = ContainerRegistry.getInstance().findInstanceContainers(type="material", GUID=material_data["guid"])
+ if containers:
+ color = containers[0].getMetaDataEntry("color_code")
+ brand = containers[0].getMetaDataEntry("brand")
+ material_type = containers[0].getMetaDataEntry("material")
+ name = containers[0].getName()
+ else:
+ Logger.log("w",
+ "Unable to find material with guid {guid}. Using data as provided by cluster".format(
+ guid=material_data["guid"]))
+ color = material_data["color"]
+ brand = material_data["brand"]
+ material_type = material_data["material"]
+ name = "Empty" if material_data["material"] == "empty" else "Unknown"
+ return MaterialOutputModel(guid=material_data["guid"], type=material_type,
+ brand=brand, color=color, name=name)
+
def _updatePrinter(self, printer: PrinterOutputModel, data: Dict[str, Any]) -> None:
# For some unknown reason the cluster wants UUID for everything, except for sending a job directly to a printer.
# Then we suddenly need the unique name. So in order to not have to mess up all the other code, we save a mapping.
@@ -523,24 +628,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
material_data = extruder_data["material"]
if extruder.activeMaterial is None or extruder.activeMaterial.guid != material_data["guid"]:
- containers = ContainerRegistry.getInstance().findInstanceContainers(type="material",
- GUID=material_data["guid"])
- if containers:
- color = containers[0].getMetaDataEntry("color_code")
- brand = containers[0].getMetaDataEntry("brand")
- material_type = containers[0].getMetaDataEntry("material")
- name = containers[0].getName()
- else:
- Logger.log("w",
- "Unable to find material with guid {guid}. Using data as provided by cluster".format(
- guid=material_data["guid"]))
- color = material_data["color"]
- brand = material_data["brand"]
- material_type = material_data["material"]
- name = "Empty" if material_data["material"] == "empty" else "Unknown"
-
- material = MaterialOutputModel(guid=material_data["guid"], type=material_type,
- brand=brand, color=color, name=name)
+ material = self._createMaterialOutputModel(material_data)
extruder.updateActiveMaterial(material)
def _removeJob(self, job: PrintJobOutputModel) -> bool:
@@ -568,6 +656,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
job = SendMaterialJob(device = self)
job.run()
+
def loadJsonFromReply(reply: QNetworkReply) -> Optional[List[Dict[str, Any]]]:
try:
result = json.loads(bytes(reply.readAll()).decode("utf-8"))
@@ -586,8 +675,8 @@ def checkValidGetReply(reply: QNetworkReply) -> bool:
return True
-def findByKey(list: List[Union[PrintJobOutputModel, PrinterOutputModel]], key: str) -> Optional[PrintJobOutputModel]:
- for item in list:
+def findByKey(lst: List[Union[PrintJobOutputModel, PrinterOutputModel]], key: str) -> Optional[PrintJobOutputModel]:
+ for item in lst:
if item.key == key:
return item
return None
diff --git a/plugins/UM3NetworkPrinting/ClusterUM3PrinterOutputController.py b/plugins/UM3NetworkPrinting/ClusterUM3PrinterOutputController.py
index 4a0319cafc..fcced0b883 100644
--- a/plugins/UM3NetworkPrinting/ClusterUM3PrinterOutputController.py
+++ b/plugins/UM3NetworkPrinting/ClusterUM3PrinterOutputController.py
@@ -6,8 +6,6 @@ from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
MYPY = False
if MYPY:
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
- from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
-
class ClusterUM3PrinterOutputController(PrinterOutputController):
def __init__(self, output_device):
diff --git a/plugins/UM3NetworkPrinting/PrintCoreConfiguration.qml b/plugins/UM3NetworkPrinting/PrintCoreConfiguration.qml
index 267516091b..0ae1fec920 100644
--- a/plugins/UM3NetworkPrinting/PrintCoreConfiguration.qml
+++ b/plugins/UM3NetworkPrinting/PrintCoreConfiguration.qml
@@ -12,22 +12,82 @@ Item
width: Math.round(parent.width / 2)
height: childrenRect.height
- Label
+
+ Item
{
- id: materialLabel
- text: printCoreConfiguration.activeMaterial != null ? printCoreConfiguration.activeMaterial.name : ""
- elide: Text.ElideRight
- width: parent.width
- font: UM.Theme.getFont("very_small")
+ id: extruderCircle
+ width: 30
+ height: 30
+
+ anchors.verticalCenter: printAndMaterialLabel.verticalCenter
+ opacity:
+ {
+ if(printCoreConfiguration == null || printCoreConfiguration.activeMaterial == null || printCoreConfiguration.hotendID == null)
+ {
+ return 0.5
+ }
+ return 1
+ }
+
+ Rectangle
+ {
+ anchors.fill: parent
+ radius: Math.round(width / 2)
+ border.width: 2
+ border.color: "black"
+ }
+
+ Label
+ {
+ anchors.centerIn: parent
+ font: UM.Theme.getFont("default_bold")
+ text: printCoreConfiguration.position + 1
+ }
}
- Label
+
+ Item
{
- id: printCoreLabel
- text: printCoreConfiguration.hotendID
- anchors.top: materialLabel.bottom
- elide: Text.ElideRight
- width: parent.width
- font: UM.Theme.getFont("very_small")
- opacity: 0.5
+ id: printAndMaterialLabel
+ anchors
+ {
+ right: parent.right
+ left: extruderCircle.right
+ margins: UM.Theme.getSize("default_margin").width
+ }
+ height: childrenRect.height
+
+ Label
+ {
+ id: materialLabel
+ text:
+ {
+ if(printCoreConfiguration != undefined && printCoreConfiguration.activeMaterial != undefined)
+ {
+ return printCoreConfiguration.activeMaterial.name
+ }
+ return ""
+ }
+ font: UM.Theme.getFont("default_bold")
+ elide: Text.ElideRight
+ width: parent.width
+ }
+
+ Label
+ {
+ id: printCoreLabel
+ text:
+ {
+ if(printCoreConfiguration != undefined && printCoreConfiguration.hotendID != undefined)
+ {
+ return printCoreConfiguration.hotendID
+ }
+ return ""
+ }
+ anchors.top: materialLabel.bottom
+ elide: Text.ElideRight
+ width: parent.width
+ opacity: 0.6
+ font: UM.Theme.getFont("default")
+ }
}
}
diff --git a/plugins/UM3NetworkPrinting/PrintJobInfoBlock.qml b/plugins/UM3NetworkPrinting/PrintJobInfoBlock.qml
new file mode 100644
index 0000000000..d50ee769d3
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/PrintJobInfoBlock.qml
@@ -0,0 +1,378 @@
+import QtQuick 2.2
+import QtQuick.Controls 2.0
+import QtQuick.Controls.Styles 1.4
+import QtGraphicalEffects 1.0
+
+import UM 1.3 as UM
+
+
+Item
+{
+ id: base
+ property var printJob: null
+ property var shadowRadius: 5
+ function getPrettyTime(time)
+ {
+ return OutputDevice.formatDuration(time)
+ }
+
+ UM.I18nCatalog
+ {
+ id: catalog
+ name: "cura"
+ }
+
+ Rectangle
+ {
+ id: background
+ anchors
+ {
+ top: parent.top
+ topMargin: 3
+ left: parent.left
+ leftMargin: base.shadowRadius
+ rightMargin: base.shadowRadius
+ right: parent.right
+ bottom: parent.bottom
+ bottomMargin: base.shadowRadius
+ }
+
+ layer.enabled: true
+ layer.effect: DropShadow
+ {
+ radius: base.shadowRadius
+ verticalOffset: 2
+ color: "#3F000000" // 25% shadow
+ }
+
+ Item
+ {
+ // Content on the left of the infobox
+ anchors
+ {
+ top: parent.top
+ bottom: parent.bottom
+ left: parent.left
+ right: parent.horizontalCenter
+ margins: 2 * UM.Theme.getSize("default_margin").width
+ rightMargin: UM.Theme.getSize("default_margin").width
+ }
+
+ Label
+ {
+ id: printJobName
+ text: printJob.name
+ font: UM.Theme.getFont("default_bold")
+ width: parent.width
+ elide: Text.ElideRight
+ }
+
+ Label
+ {
+ id: ownerName
+ anchors.top: printJobName.bottom
+ text: printJob.owner
+ font: UM.Theme.getFont("default")
+ opacity: 0.6
+ width: parent.width
+ elide: Text.ElideRight
+ }
+
+ Image
+ {
+ id: printJobPreview
+ source: printJob.previewImageUrl
+ anchors.top: ownerName.bottom
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.bottom: totalTimeLabel.bottom
+ width: height
+ opacity: printJob.state == "error" ? 0.5 : 1.0
+ }
+
+ UM.RecolorImage
+ {
+ id: statusImage
+ anchors.centerIn: printJobPreview
+ source: printJob.state == "error" ? "aborted-icon.svg" : ""
+ visible: source != ""
+ width: 0.5 * printJobPreview.width
+ height: 0.5 * printJobPreview.height
+ sourceSize.width: width
+ sourceSize.height: height
+ color: "black"
+ }
+
+ Label
+ {
+ id: totalTimeLabel
+ opacity: 0.6
+ anchors.bottom: parent.bottom
+ anchors.right: parent.right
+ font: UM.Theme.getFont("default")
+ text: printJob != null ? getPrettyTime(printJob.timeTotal) : ""
+ elide: Text.ElideRight
+ }
+ }
+
+ Item
+ {
+ // Content on the right side of the infobox.
+ anchors
+ {
+ top: parent.top
+ bottom: parent.bottom
+ left: parent.horizontalCenter
+ right: parent.right
+ margins: 2 * UM.Theme.getSize("default_margin").width
+ leftMargin: UM.Theme.getSize("default_margin").width
+ }
+
+ Label
+ {
+ id: targetPrinterLabel
+ elide: Text.ElideRight
+ font: UM.Theme.getFont("default_bold")
+ text:
+ {
+ if(printJob.assignedPrinter == null)
+ {
+ if(printJob.state == "error")
+ {
+ return catalog.i18nc("@label", "Waiting for: Unavailable printer")
+ }
+ return catalog.i18nc("@label", "Waiting for: First available")
+ }
+ else
+ {
+ return catalog.i18nc("@label", "Waiting for: ") + printJob.assignedPrinter.name
+ }
+
+ }
+
+ anchors
+ {
+ left: parent.left
+ right: contextButton.left
+ rightMargin: UM.Theme.getSize("default_margin").width
+ }
+ }
+
+
+ function switchPopupState()
+ {
+ popup.visible ? popup.close() : popup.open()
+ }
+
+ Button
+ {
+ id: contextButton
+ text: "\u22EE" //Unicode; Three stacked points.
+ font.pixelSize: 25
+ width: 35
+ height: width
+ anchors
+ {
+ right: parent.right
+ top: parent.top
+ }
+ hoverEnabled: true
+
+ background: Rectangle
+ {
+ opacity: contextButton.down || contextButton.hovered ? 1 : 0
+ width: contextButton.width
+ height: contextButton.height
+ radius: 0.5 * width
+ color: UM.Theme.getColor("viewport_background")
+ }
+
+ onClicked: parent.switchPopupState()
+ }
+
+ Popup
+ {
+ // TODO Change once updating to Qt5.10 - The 'opened' property is in 5.10 but the behavior is now implemented with the visible property
+ id: popup
+ clip: true
+ closePolicy: Popup.CloseOnPressOutsideParent
+ x: parent.width - width
+ y: contextButton.height
+ width: 160
+ height: contentItem.height + 2 * padding
+ visible: false
+
+ transformOrigin: Popup.Top
+ contentItem: Item
+ {
+ width: popup.width - 2 * popup.padding
+ height: childrenRect.height + 15
+ Button
+ {
+ id: sendToTopButton
+ text: catalog.i18nc("@label", "Move to top")
+ onClicked:
+ {
+ OutputDevice.sendJobToTop(printJob.key)
+ popup.close()
+ }
+ width: parent.width
+ enabled: OutputDevice.queuedPrintJobs[0].key != printJob.key
+ anchors.top: parent.top
+ anchors.topMargin: 10
+ hoverEnabled: true
+ background: Rectangle
+ {
+ opacity: sendToTopButton.down || sendToTopButton.hovered ? 1 : 0
+ color: UM.Theme.getColor("viewport_background")
+ }
+ }
+
+ Button
+ {
+ id: deleteButton
+ text: catalog.i18nc("@label", "Delete")
+ onClicked:
+ {
+ OutputDevice.deleteJobFromQueue(printJob.key)
+ popup.close()
+ }
+ width: parent.width
+ anchors.top: sendToTopButton.bottom
+ hoverEnabled: true
+ background: Rectangle
+ {
+ opacity: deleteButton.down || deleteButton.hovered ? 1 : 0
+ color: UM.Theme.getColor("viewport_background")
+ }
+ }
+ }
+
+ background: Item
+ {
+ width: popup.width
+ height: popup.height
+
+ DropShadow
+ {
+ anchors.fill: pointedRectangle
+ radius: 5
+ color: "#3F000000" // 25% shadow
+ source: pointedRectangle
+ transparentBorder: true
+ verticalOffset: 2
+ }
+
+ Item
+ {
+ id: pointedRectangle
+ width: parent.width -10
+ height: parent.height -10
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+
+ Rectangle
+ {
+ id: point
+ height: 13
+ width: 13
+ color: UM.Theme.getColor("setting_control")
+ transform: Rotation { angle: 45}
+ anchors.right: bloop.right
+ y: 1
+ }
+
+ Rectangle
+ {
+ id: bloop
+ color: UM.Theme.getColor("setting_control")
+ width: parent.width
+ anchors.top: parent.top
+ anchors.topMargin: 10
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: 5
+ }
+ }
+ }
+
+ exit: Transition
+ {
+ // This applies a default NumberAnimation to any changes a state change makes to x or y properties
+ NumberAnimation { property: "visible"; duration: 75; }
+ }
+ enter: Transition
+ {
+ // This applies a default NumberAnimation to any changes a state change makes to x or y properties
+ NumberAnimation { property: "visible"; duration: 75; }
+ }
+
+ onClosed: visible = false
+ onOpened: visible = true
+ }
+
+ Row
+ {
+ id: printerFamilyPills
+ spacing: 0.5 * UM.Theme.getSize("default_margin").width
+ anchors
+ {
+ left: parent.left
+ right: parent.right
+ bottom: extrudersInfo.top
+ bottomMargin: UM.Theme.getSize("default_margin").height
+ }
+ height: childrenRect.height
+ Repeater
+ {
+ model: printJob.compatibleMachineFamilies
+
+ delegate: PrinterFamilyPill
+ {
+ text: modelData
+ color: UM.Theme.getColor("viewport_background")
+ padding: 3
+ }
+ }
+ }
+ // PrintCore && Material config
+ Row
+ {
+ id: extrudersInfo
+ anchors.bottom: parent.bottom
+
+ anchors
+ {
+ left: parent.left
+ right: parent.right
+ }
+ height: childrenRect.height
+
+ spacing: UM.Theme.getSize("default_margin").width
+
+ PrintCoreConfiguration
+ {
+ id: leftExtruderInfo
+ width: Math.round(parent.width / 2)
+ printCoreConfiguration: printJob.configuration.extruderConfigurations[0]
+ }
+
+ PrintCoreConfiguration
+ {
+ id: rightExtruderInfo
+ width: Math.round(parent.width / 2)
+ printCoreConfiguration: printJob.configuration.extruderConfigurations[1]
+ }
+ }
+
+ }
+
+ Rectangle
+ {
+ color: UM.Theme.getColor("viewport_background")
+ width: 2
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.margins: UM.Theme.getSize("default_margin").height
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/PrinterFamilyPill.qml b/plugins/UM3NetworkPrinting/PrinterFamilyPill.qml
new file mode 100644
index 0000000000..b785cd02b7
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/PrinterFamilyPill.qml
@@ -0,0 +1,28 @@
+import QtQuick 2.2
+import QtQuick.Controls 1.4
+import UM 1.2 as UM
+
+Item
+{
+ property alias color: background.color
+ property alias text: familyNameLabel.text
+ property var padding: 0
+ implicitHeight: familyNameLabel.contentHeight + 2 * padding // Apply the padding to top and bottom.
+ implicitWidth: familyNameLabel.contentWidth + implicitHeight // The extra height is added to ensure the radius doesn't cut something off.
+ Rectangle
+ {
+ id: background
+ height: parent.height
+ width: parent.width
+ color: parent.color
+ anchors.right: parent.right
+ anchors.horizontalCenter: parent.horizontalCenter
+ radius: 0.5 * height
+ }
+ Label
+ {
+ id: familyNameLabel
+ anchors.centerIn: parent
+ text: ""
+ }
+}
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/PrinterVideoStream.qml b/plugins/UM3NetworkPrinting/PrinterVideoStream.qml
index 68758e095e..74c8ec8483 100644
--- a/plugins/UM3NetworkPrinting/PrinterVideoStream.qml
+++ b/plugins/UM3NetworkPrinting/PrinterVideoStream.qml
@@ -7,6 +7,8 @@ import UM 1.3 as UM
Item
{
+ property var camera: null
+
Rectangle
{
anchors.fill:parent
@@ -17,7 +19,7 @@ Item
MouseArea
{
anchors.fill: parent
- onClicked: OutputDevice.setActivePrinter(null)
+ onClicked: OutputDevice.setActiveCamera(null)
z: 0
}
@@ -32,7 +34,7 @@ Item
width: 20 * screenScaleFactor
height: 20 * screenScaleFactor
- onClicked: OutputDevice.setActivePrinter(null)
+ onClicked: OutputDevice.setActiveCamera(null)
style: ButtonStyle
{
@@ -65,23 +67,24 @@ Item
{
if(visible)
{
- if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null)
+ if(camera != null)
{
- OutputDevice.activePrinter.camera.start()
+ camera.start()
}
} else
{
- if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null)
+ if(camera != null)
{
- OutputDevice.activePrinter.camera.stop()
+ camera.stop()
}
}
}
+
source:
{
- if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null && OutputDevice.activePrinter.camera.latestImage)
+ if(camera != null && camera.latestImage != null)
{
- return OutputDevice.activePrinter.camera.latestImage;
+ return camera.latestImage;
}
return "";
}
@@ -92,7 +95,7 @@ Item
anchors.fill: cameraImage
onClicked:
{
- OutputDevice.setActivePrinter(null)
+ OutputDevice.setActiveCamera(null)
}
z: 1
}
diff --git a/plugins/UM3NetworkPrinting/UM3-icon.svg b/plugins/UM3NetworkPrinting/UM3-icon.svg
new file mode 100644
index 0000000000..6b5d4e4895
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/UM3-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/UM3x-icon.svg b/plugins/UM3NetworkPrinting/UM3x-icon.svg
new file mode 100644
index 0000000000..3708173dc5
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/UM3x-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/UMs5-icon.svg b/plugins/UM3NetworkPrinting/UMs5-icon.svg
new file mode 100644
index 0000000000..78437465b3
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/UMs5-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/aborted-icon.svg b/plugins/UM3NetworkPrinting/aborted-icon.svg
new file mode 100644
index 0000000000..7ef82c8911
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/aborted-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/approved-icon.svg b/plugins/UM3NetworkPrinting/approved-icon.svg
new file mode 100644
index 0000000000..671957d709
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/approved-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/paused-icon.svg b/plugins/UM3NetworkPrinting/paused-icon.svg
new file mode 100644
index 0000000000..a66217d662
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/paused-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/warning-icon.svg b/plugins/UM3NetworkPrinting/warning-icon.svg
new file mode 100644
index 0000000000..1e5359a5eb
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/warning-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/qml/SidebarHeader.qml b/resources/qml/SidebarHeader.qml
index 6ee33dd2f2..3a041ae499 100644
--- a/resources/qml/SidebarHeader.qml
+++ b/resources/qml/SidebarHeader.qml
@@ -274,7 +274,7 @@ Column
elide: Text.ElideRight
}
- // Everthing for the extruder icon
+ // Everything for the extruder icon
Item
{
id: extruderIconItem