Merge pull request #4301 from Ultimaker/cura_connect_UI_rework
CL-893 CL-894 Cura connect ui rework
@ -19,5 +19,11 @@ class CameraImageProvider(QQuickImageProvider):
|
|||||||
|
|
||||||
return image, QSize(15, 15)
|
return image, QSize(15, 15)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
try:
|
||||||
|
image = output_device.activeCamera.getImage()
|
||||||
|
|
||||||
|
return image, QSize(15, 15)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
return QImage(), QSize(15, 15)
|
return QImage(), QSize(15, 15)
|
||||||
|
@ -93,6 +93,7 @@ from . import CuraActions
|
|||||||
from cura.Scene import ZOffsetDecorator
|
from cura.Scene import ZOffsetDecorator
|
||||||
from . import CuraSplashScreen
|
from . import CuraSplashScreen
|
||||||
from . import CameraImageProvider
|
from . import CameraImageProvider
|
||||||
|
from . import PrintJobPreviewImageProvider
|
||||||
from . import MachineActionManager
|
from . import MachineActionManager
|
||||||
|
|
||||||
from cura.TaskManagement.OnExitCallbackManager import OnExitCallbackManager
|
from cura.TaskManagement.OnExitCallbackManager import OnExitCallbackManager
|
||||||
@ -502,6 +503,7 @@ class CuraApplication(QtApplication):
|
|||||||
|
|
||||||
def _onEngineCreated(self):
|
def _onEngineCreated(self):
|
||||||
self._qml_engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
|
self._qml_engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
|
||||||
|
self._qml_engine.addImageProvider("print_job_preview", PrintJobPreviewImageProvider.PrintJobPreviewImageProvider())
|
||||||
|
|
||||||
@pyqtProperty(bool)
|
@pyqtProperty(bool)
|
||||||
def needToShowUserAgreement(self):
|
def needToShowUserAgreement(self):
|
||||||
|
27
cura/PrintJobPreviewImageProvider.py
Normal file
@ -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)
|
@ -27,7 +27,13 @@ class ConfigurationModel(QObject):
|
|||||||
return self._printer_type
|
return self._printer_type
|
||||||
|
|
||||||
def setExtruderConfigurations(self, extruder_configurations):
|
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)
|
@pyqtProperty("QVariantList", fset = setExtruderConfigurations, notify = configurationChanged)
|
||||||
def extruderConfigurations(self):
|
def extruderConfigurations(self):
|
||||||
|
@ -1,56 +1,67 @@
|
|||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal
|
from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal
|
||||||
|
|
||||||
|
from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
|
||||||
|
|
||||||
|
|
||||||
class ExtruderConfigurationModel(QObject):
|
class ExtruderConfigurationModel(QObject):
|
||||||
|
|
||||||
extruderConfigurationChanged = pyqtSignal()
|
extruderConfigurationChanged = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, position: int = -1) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._position = -1
|
self._position = position # type: int
|
||||||
self._material = None
|
self._material = None # type: Optional[MaterialOutputModel]
|
||||||
self._hotend_id = None
|
self._hotend_id = None # type: Optional[str]
|
||||||
|
|
||||||
def setPosition(self, position):
|
def setPosition(self, position: int) -> None:
|
||||||
self._position = position
|
self._position = position
|
||||||
|
|
||||||
@pyqtProperty(int, fset = setPosition, notify = extruderConfigurationChanged)
|
@pyqtProperty(int, fset = setPosition, notify = extruderConfigurationChanged)
|
||||||
def position(self):
|
def position(self) -> int:
|
||||||
return self._position
|
return self._position
|
||||||
|
|
||||||
def setMaterial(self, material):
|
def setMaterial(self, material: Optional[MaterialOutputModel]) -> None:
|
||||||
self._material = material
|
if self._hotend_id != material:
|
||||||
|
self._material = material
|
||||||
|
self.extruderConfigurationChanged.emit()
|
||||||
|
|
||||||
@pyqtProperty(QObject, fset = setMaterial, notify = extruderConfigurationChanged)
|
@pyqtProperty(QObject, fset = setMaterial, notify = extruderConfigurationChanged)
|
||||||
def material(self):
|
def activeMaterial(self) -> Optional[MaterialOutputModel]:
|
||||||
return self._material
|
return self._material
|
||||||
|
|
||||||
def setHotendID(self, hotend_id):
|
@pyqtProperty(QObject, fset=setMaterial, notify=extruderConfigurationChanged)
|
||||||
self._hotend_id = hotend_id
|
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)
|
@pyqtProperty(str, fset = setHotendID, notify = extruderConfigurationChanged)
|
||||||
def hotendID(self):
|
def hotendID(self) -> Optional[str]:
|
||||||
return self._hotend_id
|
return self._hotend_id
|
||||||
|
|
||||||
## This method is intended to indicate whether the configuration is valid or not.
|
## This method is intended to indicate whether the configuration is valid or not.
|
||||||
# The method checks if the mandatory fields are or not set
|
# 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.
|
# At this moment is always valid since we allow to have empty material and variants.
|
||||||
def isValid(self):
|
def isValid(self) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
message_chunks = []
|
message_chunks = []
|
||||||
message_chunks.append("Position: " + str(self._position))
|
message_chunks.append("Position: " + str(self._position))
|
||||||
message_chunks.append("-")
|
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("-")
|
||||||
message_chunks.append("HotendID: " + self.hotendID if self.hotendID else "empty")
|
message_chunks.append("HotendID: " + self.hotendID if self.hotendID else "empty")
|
||||||
return " ".join(message_chunks)
|
return " ".join(message_chunks)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other) -> bool:
|
||||||
return hash(self) == hash(other)
|
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
|
# Calculating a hash function using the position of the extruder, the material GUID and the hotend id to check if is
|
||||||
|
@ -12,64 +12,61 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class ExtruderOutputModel(QObject):
|
class ExtruderOutputModel(QObject):
|
||||||
hotendIDChanged = pyqtSignal()
|
|
||||||
targetHotendTemperatureChanged = pyqtSignal()
|
targetHotendTemperatureChanged = pyqtSignal()
|
||||||
hotendTemperatureChanged = pyqtSignal()
|
hotendTemperatureChanged = pyqtSignal()
|
||||||
activeMaterialChanged = pyqtSignal()
|
|
||||||
extruderConfigurationChanged = pyqtSignal()
|
extruderConfigurationChanged = pyqtSignal()
|
||||||
isPreheatingChanged = 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)
|
super().__init__(parent)
|
||||||
self._printer = printer
|
self._printer = printer # type: PrinterOutputModel
|
||||||
self._position = position
|
self._position = position
|
||||||
self._target_hotend_temperature = 0 # type: float
|
self._target_hotend_temperature = 0.0 # type: float
|
||||||
self._hotend_temperature = 0 # type: float
|
self._hotend_temperature = 0.0 # type: float
|
||||||
self._hotend_id = ""
|
|
||||||
self._active_material = None # type: Optional[MaterialOutputModel]
|
|
||||||
self._extruder_configuration = ExtruderConfigurationModel()
|
|
||||||
self._extruder_configuration.position = self._position
|
|
||||||
|
|
||||||
self._is_preheating = False
|
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
|
return self._printer
|
||||||
|
|
||||||
def getPosition(self):
|
def getPosition(self) -> int:
|
||||||
return self._position
|
return self._position
|
||||||
|
|
||||||
# Does the printer support pre-heating the bed at all
|
# Does the printer support pre-heating the bed at all
|
||||||
@pyqtProperty(bool, constant=True)
|
@pyqtProperty(bool, constant=True)
|
||||||
def canPreHeatHotends(self):
|
def canPreHeatHotends(self) -> bool:
|
||||||
if self._printer:
|
if self._printer:
|
||||||
return self._printer.canPreHeatHotends
|
return self._printer.canPreHeatHotends
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@pyqtProperty(QObject, notify = activeMaterialChanged)
|
@pyqtProperty(QObject, notify = extruderConfigurationChanged)
|
||||||
def activeMaterial(self) -> Optional["MaterialOutputModel"]:
|
def activeMaterial(self) -> Optional["MaterialOutputModel"]:
|
||||||
return self._active_material
|
return self._extruder_configuration.activeMaterial
|
||||||
|
|
||||||
def updateActiveMaterial(self, material: Optional["MaterialOutputModel"]):
|
def updateActiveMaterial(self, material: Optional["MaterialOutputModel"]) -> None:
|
||||||
if self._active_material != material:
|
self._extruder_configuration.setMaterial(material)
|
||||||
self._active_material = material
|
|
||||||
self._extruder_configuration.material = self._active_material
|
|
||||||
self.activeMaterialChanged.emit()
|
|
||||||
self.extruderConfigurationChanged.emit()
|
|
||||||
|
|
||||||
## Update the hotend temperature. This only changes it locally.
|
## 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:
|
if self._hotend_temperature != temperature:
|
||||||
self._hotend_temperature = temperature
|
self._hotend_temperature = temperature
|
||||||
self.hotendTemperatureChanged.emit()
|
self.hotendTemperatureChanged.emit()
|
||||||
|
|
||||||
def updateTargetHotendTemperature(self, temperature: float):
|
def updateTargetHotendTemperature(self, temperature: float) -> None:
|
||||||
if self._target_hotend_temperature != temperature:
|
if self._target_hotend_temperature != temperature:
|
||||||
self._target_hotend_temperature = temperature
|
self._target_hotend_temperature = temperature
|
||||||
self.targetHotendTemperatureChanged.emit()
|
self.targetHotendTemperatureChanged.emit()
|
||||||
|
|
||||||
## Set the target hotend temperature. This ensures that it's actually sent to the remote.
|
## Set the target hotend temperature. This ensures that it's actually sent to the remote.
|
||||||
@pyqtSlot(float)
|
@pyqtSlot(float)
|
||||||
def setTargetHotendTemperature(self, temperature: float):
|
def setTargetHotendTemperature(self, temperature: float) -> None:
|
||||||
self._printer.getController().setTargetHotendTemperature(self._printer, self, temperature)
|
self._printer.getController().setTargetHotendTemperature(self._printer, self, temperature)
|
||||||
self.updateTargetHotendTemperature(temperature)
|
self.updateTargetHotendTemperature(temperature)
|
||||||
|
|
||||||
@ -81,30 +78,26 @@ class ExtruderOutputModel(QObject):
|
|||||||
def hotendTemperature(self) -> float:
|
def hotendTemperature(self) -> float:
|
||||||
return self._hotend_temperature
|
return self._hotend_temperature
|
||||||
|
|
||||||
@pyqtProperty(str, notify = hotendIDChanged)
|
@pyqtProperty(str, notify = extruderConfigurationChanged)
|
||||||
def hotendID(self) -> str:
|
def hotendID(self) -> str:
|
||||||
return self._hotend_id
|
return self._extruder_configuration.hotendID
|
||||||
|
|
||||||
def updateHotendID(self, id: str):
|
def updateHotendID(self, hotend_id: str) -> None:
|
||||||
if self._hotend_id != id:
|
self._extruder_configuration.setHotendID(hotend_id)
|
||||||
self._hotend_id = id
|
|
||||||
self._extruder_configuration.hotendID = self._hotend_id
|
|
||||||
self.hotendIDChanged.emit()
|
|
||||||
self.extruderConfigurationChanged.emit()
|
|
||||||
|
|
||||||
@pyqtProperty(QObject, notify = extruderConfigurationChanged)
|
@pyqtProperty(QObject, notify = extruderConfigurationChanged)
|
||||||
def extruderConfiguration(self):
|
def extruderConfiguration(self) -> Optional[ExtruderConfigurationModel]:
|
||||||
if self._extruder_configuration.isValid():
|
if self._extruder_configuration.isValid():
|
||||||
return self._extruder_configuration
|
return self._extruder_configuration
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def updateIsPreheating(self, pre_heating):
|
def updateIsPreheating(self, pre_heating: bool) -> None:
|
||||||
if self._is_preheating != pre_heating:
|
if self._is_preheating != pre_heating:
|
||||||
self._is_preheating = pre_heating
|
self._is_preheating = pre_heating
|
||||||
self.isPreheatingChanged.emit()
|
self.isPreheatingChanged.emit()
|
||||||
|
|
||||||
@pyqtProperty(bool, notify=isPreheatingChanged)
|
@pyqtProperty(bool, notify=isPreheatingChanged)
|
||||||
def isPreheating(self):
|
def isPreheating(self) -> bool:
|
||||||
return self._is_preheating
|
return self._is_preheating
|
||||||
|
|
||||||
## Pre-heats the extruder before printer.
|
## Pre-heats the extruder before printer.
|
||||||
@ -113,9 +106,9 @@ class ExtruderOutputModel(QObject):
|
|||||||
# Celsius.
|
# Celsius.
|
||||||
# \param duration How long the bed should stay warm, in seconds.
|
# \param duration How long the bed should stay warm, in seconds.
|
||||||
@pyqtSlot(float, float)
|
@pyqtSlot(float, float)
|
||||||
def preheatHotend(self, temperature, duration):
|
def preheatHotend(self, temperature: float, duration: float) -> None:
|
||||||
self._printer._controller.preheatHotend(self, temperature, duration)
|
self._printer._controller.preheatHotend(self, temperature, duration)
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def cancelPreheatHotend(self):
|
def cancelPreheatHotend(self) -> None:
|
||||||
self._printer._controller.cancelPreheatHotend(self)
|
self._printer._controller.cancelPreheatHotend(self)
|
||||||
|
@ -188,40 +188,55 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
|||||||
if reply in self._kept_alive_multiparts:
|
if reply in self._kept_alive_multiparts:
|
||||||
del self._kept_alive_multiparts[reply]
|
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:
|
if self._manager is None:
|
||||||
self._createNetworkManager()
|
self._createNetworkManager()
|
||||||
assert (self._manager is not None)
|
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)
|
request = self._createEmptyRequest(target)
|
||||||
self._last_request_time = time()
|
self._last_request_time = time()
|
||||||
reply = self._manager.put(request, data.encode())
|
if self._manager is not None:
|
||||||
self._registerOnFinishedCallback(reply, on_finished)
|
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:
|
def get(self, target: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
|
||||||
if self._manager is None:
|
self._validateManager()
|
||||||
self._createNetworkManager()
|
|
||||||
assert (self._manager is not None)
|
|
||||||
request = self._createEmptyRequest(target)
|
request = self._createEmptyRequest(target)
|
||||||
self._last_request_time = time()
|
self._last_request_time = time()
|
||||||
reply = self._manager.get(request)
|
if self._manager is not None:
|
||||||
self._registerOnFinishedCallback(reply, on_finished)
|
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:
|
def post(self, target: str, data: str, on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> None:
|
||||||
if self._manager is None:
|
self._validateManager()
|
||||||
self._createNetworkManager()
|
|
||||||
assert (self._manager is not None)
|
|
||||||
request = self._createEmptyRequest(target)
|
request = self._createEmptyRequest(target)
|
||||||
self._last_request_time = time()
|
self._last_request_time = time()
|
||||||
reply = self._manager.post(request, data)
|
if self._manager is not None:
|
||||||
if on_progress is not None:
|
reply = self._manager.post(request, data)
|
||||||
reply.uploadProgress.connect(on_progress)
|
if on_progress is not None:
|
||||||
self._registerOnFinishedCallback(reply, on_finished)
|
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:
|
def postFormWithParts(self, target: str, parts: List[QHttpPart], on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> QNetworkReply:
|
||||||
|
self._validateManager()
|
||||||
if self._manager is None:
|
|
||||||
self._createNetworkManager()
|
|
||||||
assert (self._manager is not None)
|
|
||||||
request = self._createEmptyRequest(target, content_type=None)
|
request = self._createEmptyRequest(target, content_type=None)
|
||||||
multi_post_part = QHttpMultiPart(QHttpMultiPart.FormDataType)
|
multi_post_part = QHttpMultiPart(QHttpMultiPart.FormDataType)
|
||||||
for part in parts:
|
for part in parts:
|
||||||
@ -229,15 +244,18 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
|||||||
|
|
||||||
self._last_request_time = time()
|
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:
|
if on_progress is not None:
|
||||||
reply.uploadProgress.connect(on_progress)
|
reply.uploadProgress.connect(on_progress)
|
||||||
self._registerOnFinishedCallback(reply, on_finished)
|
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:
|
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()
|
post_part = QHttpPart()
|
||||||
|
@ -2,11 +2,15 @@
|
|||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot
|
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:
|
if TYPE_CHECKING:
|
||||||
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
|
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
|
||||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||||
|
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
|
||||||
|
|
||||||
|
|
||||||
class PrintJobOutputModel(QObject):
|
class PrintJobOutputModel(QObject):
|
||||||
@ -17,6 +21,9 @@ class PrintJobOutputModel(QObject):
|
|||||||
keyChanged = pyqtSignal()
|
keyChanged = pyqtSignal()
|
||||||
assignedPrinterChanged = pyqtSignal()
|
assignedPrinterChanged = pyqtSignal()
|
||||||
ownerChanged = pyqtSignal()
|
ownerChanged = pyqtSignal()
|
||||||
|
configurationChanged = pyqtSignal()
|
||||||
|
previewImageChanged = pyqtSignal()
|
||||||
|
compatibleMachineFamiliesChanged = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, output_controller: "PrinterOutputController", key: str = "", name: str = "", parent=None) -> None:
|
def __init__(self, output_controller: "PrinterOutputController", key: str = "", name: str = "", parent=None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
@ -29,6 +36,48 @@ class PrintJobOutputModel(QObject):
|
|||||||
self._assigned_printer = None # type: Optional[PrinterOutputModel]
|
self._assigned_printer = None # type: Optional[PrinterOutputModel]
|
||||||
self._owner = "" # Who started/owns the print job?
|
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)
|
@pyqtProperty(str, notify=ownerChanged)
|
||||||
def owner(self):
|
def owner(self):
|
||||||
return self._owner
|
return self._owner
|
||||||
|
@ -35,7 +35,7 @@ class PrinterOutputModel(QObject):
|
|||||||
self._key = "" # Unique identifier
|
self._key = "" # Unique identifier
|
||||||
self._controller = output_controller
|
self._controller = output_controller
|
||||||
self._extruders = [ExtruderOutputModel(printer = self, position = i) for i in range(number_of_extruders)]
|
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._head_position = Vector(0, 0, 0)
|
||||||
self._active_print_job = None # type: Optional[PrintJobOutputModel]
|
self._active_print_job = None # type: Optional[PrintJobOutputModel]
|
||||||
self._firmware_version = firmware_version
|
self._firmware_version = firmware_version
|
||||||
@ -43,9 +43,9 @@ class PrinterOutputModel(QObject):
|
|||||||
self._is_preheating = False
|
self._is_preheating = False
|
||||||
self._printer_type = ""
|
self._printer_type = ""
|
||||||
self._buildplate_name = None
|
self._buildplate_name = None
|
||||||
# Update the printer configuration every time any of the extruders changes its configuration
|
|
||||||
for extruder in self._extruders:
|
self._printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in
|
||||||
extruder.extruderConfigurationChanged.connect(self._updateExtruderConfiguration)
|
self._extruders]
|
||||||
|
|
||||||
self._camera = None
|
self._camera = None
|
||||||
|
|
||||||
@ -282,8 +282,4 @@ class PrinterOutputModel(QObject):
|
|||||||
def printerConfiguration(self):
|
def printerConfiguration(self):
|
||||||
if self._printer_configuration.isValid():
|
if self._printer_configuration.isValid():
|
||||||
return self._printer_configuration
|
return self._printer_configuration
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _updateExtruderConfiguration(self):
|
|
||||||
self._printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in self._extruders]
|
|
||||||
self.configurationChanged.emit()
|
|
@ -1,21 +1,26 @@
|
|||||||
import QtQuick 2.2
|
import QtQuick 2.3
|
||||||
import QtQuick.Controls 1.4
|
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 UM 1.3 as UM
|
||||||
import Cura 1.0 as Cura
|
import Cura 1.0 as Cura
|
||||||
|
|
||||||
|
|
||||||
Component
|
Component
|
||||||
{
|
{
|
||||||
Rectangle
|
Rectangle
|
||||||
{
|
{
|
||||||
id: base
|
id: base
|
||||||
property var manager: Cura.MachineManager.printerOutputDevices[0]
|
|
||||||
property var lineColor: "#DCDCDC" // TODO: Should be linked to theme.
|
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
|
anchors.fill: parent
|
||||||
color: UM.Theme.getColor("viewport_background")
|
color: "white"
|
||||||
|
|
||||||
UM.I18nCatalog
|
UM.I18nCatalog
|
||||||
{
|
{
|
||||||
@ -25,217 +30,689 @@ Component
|
|||||||
|
|
||||||
Label
|
Label
|
||||||
{
|
{
|
||||||
id: activePrintersLabel
|
id: printingLabel
|
||||||
font: UM.Theme.getFont("large")
|
font: UM.Theme.getFont("large")
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors
|
||||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
{
|
||||||
anchors.top: parent.top
|
margins: 2 * UM.Theme.getSize("default_margin").width
|
||||||
anchors.left: parent.left
|
leftMargin: 4 * UM.Theme.getSize("default_margin").width
|
||||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
top: parent.top
|
||||||
anchors.right:parent.right
|
left: parent.left
|
||||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
right: parent.right
|
||||||
text: Cura.MachineManager.printerOutputDevices[0].name
|
}
|
||||||
|
|
||||||
|
text: catalog.i18nc("@label", "Printing")
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle
|
Label
|
||||||
{
|
{
|
||||||
id: printJobArea
|
id: managePrintersLabel
|
||||||
border.width: UM.Theme.getSize("default_lining").width
|
anchors.rightMargin: 4 * UM.Theme.getSize("default_margin").width
|
||||||
border.color: lineColor
|
anchors.right: printerScrollView.right
|
||||||
anchors.top: activePrintersLabel.bottom
|
anchors.bottom: printingLabel.bottom
|
||||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
text: catalog.i18nc("@label link to connect manager", "Manage printers")
|
||||||
anchors.left: parent.left
|
font: UM.Theme.getFont("default")
|
||||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
color: UM.Theme.getColor("primary")
|
||||||
anchors.right: parent.right
|
linkColor: UM.Theme.getColor("primary")
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MouseArea
|
||||||
Rectangle
|
|
||||||
{
|
{
|
||||||
id: printersArea
|
anchors.fill: managePrintersLabel
|
||||||
border.width: UM.Theme.getSize("default_lining").width
|
hoverEnabled: true
|
||||||
border.color: lineColor
|
onClicked: Cura.MachineManager.printerOutputDevices[0].openPrinterControlPanel()
|
||||||
anchors.top: printJobArea.bottom
|
onEntered: managePrintersLabel.font.underline = true
|
||||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
onExited: managePrintersLabel.font.underline = false
|
||||||
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
|
ScrollView
|
||||||
|
{
|
||||||
|
id: printerScrollView
|
||||||
|
anchors
|
||||||
{
|
{
|
||||||
id: printersTitleBar
|
top: printingLabel.bottom
|
||||||
width: parent.width
|
left: parent.left
|
||||||
height: printJobTitleLabel.height + 2 * UM.Theme.getSize("default_margin").height
|
right: parent.right
|
||||||
|
topMargin: UM.Theme.getSize("default_margin").height
|
||||||
Label
|
bottom: parent.bottom
|
||||||
{
|
bottomMargin: UM.Theme.getSize("default_margin").height
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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
|
top: parent.top
|
||||||
Item
|
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
|
width: parent.width - 2 * shadowRadius
|
||||||
height: childrenRect.height
|
height: childrenRect.height + UM.Theme.getSize("default_margin").height
|
||||||
opacity: 0.65
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
Label
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
id: base
|
||||||
|
property var shadowRadius: 5
|
||||||
|
property var collapsed: true
|
||||||
|
|
||||||
|
layer.enabled: true
|
||||||
|
layer.effect: DropShadow
|
||||||
{
|
{
|
||||||
text: modelData.machine_type
|
radius: base.shadowRadius
|
||||||
font: UM.Theme.getFont("very_small")
|
verticalOffset: 2
|
||||||
|
color: "#3F000000" // 25% shadow
|
||||||
}
|
}
|
||||||
|
|
||||||
Label
|
Item
|
||||||
{
|
{
|
||||||
text: modelData.count
|
id: printerInfo
|
||||||
font: UM.Theme.getFont("small")
|
height: machineIcon.height
|
||||||
anchors.right: parent.right
|
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,93 +25,83 @@ Component
|
|||||||
|
|
||||||
Label
|
Label
|
||||||
{
|
{
|
||||||
id: activePrintersLabel
|
id: manageQueueLabel
|
||||||
font: UM.Theme.getFont("large")
|
anchors.rightMargin: 4 * UM.Theme.getSize("default_margin").width
|
||||||
|
anchors.right: queuedPrintJobs.right
|
||||||
anchors {
|
anchors.bottom: queuedLabel.bottom
|
||||||
top: parent.top
|
text: catalog.i18nc("@label link to connect manager", "Manage queue")
|
||||||
topMargin: UM.Theme.getSize("default_margin").height * 2 // a bit more spacing to give it some breathing room
|
font: UM.Theme.getFont("default")
|
||||||
horizontalCenter: parent.horizontalCenter
|
color: UM.Theme.getColor("primary")
|
||||||
}
|
linkColor: UM.Theme.getColor("primary")
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.top: parent.top
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.topMargin: 2 * UM.Theme.getSize("default_margin").height
|
||||||
|
anchors.leftMargin: 3 * UM.Theme.getSize("default_margin").width
|
||||||
width: Math.min(800 * screenScaleFactor, maximumWidth)
|
text: catalog.i18nc("@label", "Queued")
|
||||||
height: children.height
|
font: UM.Theme.getFont("large")
|
||||||
visible: OutputDevice.printers.length != 0
|
color: UM.Theme.getColor("text")
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollView
|
ScrollView
|
||||||
{
|
{
|
||||||
id: printerScrollView
|
id: queuedPrintJobs
|
||||||
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
|
|
||||||
|
|
||||||
|
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
|
ListView
|
||||||
{
|
{
|
||||||
anchors.fill: parent
|
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
|
printJob: modelData
|
||||||
width: Math.min(800 * screenScaleFactor, maximumWidth)
|
anchors.left: parent.left
|
||||||
height: 125 * screenScaleFactor
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: UM.Theme.getSize("default_margin").height
|
||||||
// Add a 1 pix margin, as the border is sometimes cut off otherwise.
|
anchors.leftMargin: UM.Theme.getSize("default_margin").height
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
height: 175 * screenScaleFactor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PrinterVideoStream
|
PrinterVideoStream
|
||||||
{
|
{
|
||||||
visible: OutputDevice.activePrinter != null
|
visible: OutputDevice.activeCamera != null
|
||||||
anchors.fill:parent
|
anchors.fill: parent
|
||||||
|
camera: OutputDevice.activeCamera
|
||||||
}
|
}
|
||||||
|
|
||||||
onVisibleChanged:
|
onVisibleChanged:
|
||||||
{
|
{
|
||||||
if (!monitorFrame.visible)
|
if (monitorFrame != null && !monitorFrame.visible)
|
||||||
{
|
{
|
||||||
// After switching the Tab ensure that active printer is Null, the video stream image
|
OutputDevice.setActiveCamera(null)
|
||||||
// might be active
|
|
||||||
OutputDevice.setActivePrinter(null)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,19 +4,21 @@
|
|||||||
from typing import Any, cast, Optional, Set, Tuple, Union
|
from typing import Any, cast, Optional, Set, Tuple, Union
|
||||||
|
|
||||||
from UM.FileHandler.FileHandler import FileHandler
|
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.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.WriteFileJob import WriteFileJob # To call the file writer asynchronously.
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
from UM.Mesh.MeshWriter import MeshWriter # For typing
|
|
||||||
from UM.Message import Message
|
from UM.Message import Message
|
||||||
from UM.Qt.Duration import Duration, DurationFormat
|
from UM.Qt.Duration import Duration, DurationFormat
|
||||||
from UM.OutputDevice import OutputDeviceError #To show that something went wrong when writing.
|
from UM.OutputDevice import OutputDeviceError # To show that something went wrong when writing.
|
||||||
from UM.Scene.SceneNode import SceneNode #For typing.
|
from UM.Scene.SceneNode import SceneNode # For typing.
|
||||||
from UM.Version import Version #To check against firmware versions for support.
|
from UM.Version import Version # To check against firmware versions for support.
|
||||||
|
|
||||||
from cura.CuraApplication import CuraApplication
|
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.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState
|
||||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||||
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
||||||
@ -27,14 +29,14 @@ from .ClusterUM3PrinterOutputController import ClusterUM3PrinterOutputController
|
|||||||
from .SendMaterialJob import SendMaterialJob
|
from .SendMaterialJob import SendMaterialJob
|
||||||
|
|
||||||
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
|
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 PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal, pyqtProperty, QObject
|
||||||
|
|
||||||
from time import time
|
from time import time
|
||||||
from datetime import datetime
|
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 json
|
||||||
import os
|
import os
|
||||||
|
|
||||||
@ -44,6 +46,7 @@ i18n_catalog = i18nCatalog("cura")
|
|||||||
class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
printJobsChanged = pyqtSignal()
|
printJobsChanged = pyqtSignal()
|
||||||
activePrinterChanged = 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.
|
# 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.
|
# 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
|
# See comments about this hack with the clusterPrintersChanged signal
|
||||||
self.printersChanged.connect(self.clusterPrintersChanged)
|
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
|
# Cluster does not have authentication, so default to authenticated
|
||||||
self._authentication_state = AuthState.Authenticated
|
self._authentication_state = AuthState.Authenticated
|
||||||
|
|
||||||
self._error_message = None #type: Optional[Message]
|
self._error_message = None # type: Optional[Message]
|
||||||
self._write_job_progress_message = None #type: Optional[Message]
|
self._write_job_progress_message = None # type: Optional[Message]
|
||||||
self._progress_message = None #type: Optional[Message]
|
self._progress_message = None # type: Optional[Message]
|
||||||
|
|
||||||
self._active_printer = None # type: Optional[PrinterOutputModel]
|
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.setPriority(3) # Make sure the output device gets selected above local file output
|
||||||
self.setName(self._id)
|
self.setName(self._id)
|
||||||
@ -87,32 +90,35 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||||||
|
|
||||||
self._printer_uuid_to_unique_name_mapping = {} # type: Dict[str, str]
|
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:
|
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.writeStarted.emit(self)
|
||||||
|
|
||||||
self.sendMaterialProfiles()
|
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:
|
if file_handler:
|
||||||
file_formats = file_handler.getSupportedFileTypesWrite()
|
file_formats = file_handler.getSupportedFileTypesWrite()
|
||||||
else:
|
else:
|
||||||
file_formats = CuraApplication.getInstance().getMeshFileHandler().getSupportedFileTypesWrite()
|
file_formats = CuraApplication.getInstance().getMeshFileHandler().getSupportedFileTypesWrite()
|
||||||
|
|
||||||
global_stack = CuraApplication.getInstance().getGlobalContainerStack()
|
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:
|
if not global_stack:
|
||||||
Logger.log("e", "Missing global stack!")
|
Logger.log("e", "Missing global stack!")
|
||||||
return
|
return
|
||||||
|
|
||||||
machine_file_formats = global_stack.getMetaDataEntry("file_formats").split(";")
|
machine_file_formats = global_stack.getMetaDataEntry("file_formats").split(";")
|
||||||
machine_file_formats = [file_type.strip() for file_type in machine_file_formats]
|
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"):
|
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
|
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!"))
|
raise OutputDeviceError.WriteRequestFailedError(i18n_catalog.i18nc("@info:status", "There are no file formats available to write with!"))
|
||||||
preferred_format = file_formats[0]
|
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:
|
if file_handler is not None:
|
||||||
writer = file_handler.getWriterByMimeType(cast(str, preferred_format["mime_type"]))
|
writer = file_handler.getWriterByMimeType(cast(str, preferred_format["mime_type"]))
|
||||||
else:
|
else:
|
||||||
@ -135,19 +141,20 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||||||
Logger.log("e", "Unexpected error when trying to get the FileWriter")
|
Logger.log("e", "Unexpected error when trying to get the FileWriter")
|
||||||
return
|
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:
|
if not writer:
|
||||||
Logger.log("e", "Missing file or mesh writer!")
|
Logger.log("e", "Missing file or mesh writer!")
|
||||||
return
|
return
|
||||||
self._sending_job = self._sendPrintJob(writer, preferred_format, nodes)
|
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.
|
if len(self._printers) > 1: # We need to ask the user.
|
||||||
self._spawnPrinterSelectionDialog()
|
self._spawnPrinterSelectionDialog()
|
||||||
is_job_sent = True
|
is_job_sent = True
|
||||||
else: #Just immediately continue.
|
else: # Just immediately continue.
|
||||||
self._sending_job.send("") #No specifically selected printer.
|
self._sending_job.send("") # No specifically selected printer.
|
||||||
is_job_sent = self._sending_job.send(None)
|
is_job_sent = self._sending_job.send(None)
|
||||||
|
|
||||||
def _spawnPrinterSelectionDialog(self):
|
def _spawnPrinterSelectionDialog(self):
|
||||||
if self._printer_selection_dialog is None:
|
if self._printer_selection_dialog is None:
|
||||||
@ -157,7 +164,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||||||
self._printer_selection_dialog.show()
|
self._printer_selection_dialog.show()
|
||||||
|
|
||||||
@pyqtProperty(int, constant=True)
|
@pyqtProperty(int, constant=True)
|
||||||
def clusterSize(self):
|
def clusterSize(self) -> int:
|
||||||
return self._cluster_size
|
return self._cluster_size
|
||||||
|
|
||||||
## Allows the user to choose a printer to print with from the printer
|
## 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.
|
# \param target_printer The name of the printer to target.
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def selectPrinter(self, target_printer: str = "") -> None:
|
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()
|
@pyqtSlot()
|
||||||
def cancelPrintSelection(self) -> None:
|
def cancelPrintSelection(self) -> None:
|
||||||
@ -214,8 +222,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||||||
|
|
||||||
job.start()
|
job.start()
|
||||||
|
|
||||||
yield True #Return that we had success!
|
yield True # Return that we had success!
|
||||||
yield #To prevent having to catch the StopIteration exception.
|
yield # To prevent having to catch the StopIteration exception.
|
||||||
|
|
||||||
def _sendPrintJobWaitOnWriteJobFinished(self, job: WriteFileJob) -> None:
|
def _sendPrintJobWaitOnWriteJobFinished(self, job: WriteFileJob) -> None:
|
||||||
if self._write_job_progress_message:
|
if self._write_job_progress_message:
|
||||||
@ -240,7 +248,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||||||
|
|
||||||
file_name = CuraApplication.getInstance().getPrintInformation().jobName + "." + preferred_format["extension"]
|
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):
|
if isinstance(stream, io.StringIO):
|
||||||
output = cast(str, output).encode("utf-8")
|
output = cast(str, output).encode("utf-8")
|
||||||
output = cast(bytes, output)
|
output = cast(bytes, output)
|
||||||
@ -253,6 +261,10 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||||||
def activePrinter(self) -> Optional[PrinterOutputModel]:
|
def activePrinter(self) -> Optional[PrinterOutputModel]:
|
||||||
return self._active_printer
|
return self._active_printer
|
||||||
|
|
||||||
|
@pyqtProperty(QObject, notify=activeCameraChanged)
|
||||||
|
def activeCamera(self) -> Optional[NetworkCamera]:
|
||||||
|
return self._active_camera
|
||||||
|
|
||||||
@pyqtSlot(QObject)
|
@pyqtSlot(QObject)
|
||||||
def setActivePrinter(self, printer: Optional[PrinterOutputModel]) -> None:
|
def setActivePrinter(self, printer: Optional[PrinterOutputModel]) -> None:
|
||||||
if self._active_printer != printer:
|
if self._active_printer != printer:
|
||||||
@ -261,6 +273,19 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||||||
self._active_printer = printer
|
self._active_printer = printer
|
||||||
self.activePrinterChanged.emit()
|
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:
|
def _onPostPrintJobFinished(self, reply: QNetworkReply) -> None:
|
||||||
if self._progress_message:
|
if self._progress_message:
|
||||||
self._progress_message.hide()
|
self._progress_message.hide()
|
||||||
@ -279,8 +304,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||||||
|
|
||||||
# If successfully sent:
|
# If successfully sent:
|
||||||
if bytes_sent == bytes_total:
|
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
|
# Show a confirmation to the user so they know the job was sucessful and provide the option to switch to
|
||||||
# monitor tab.
|
# the monitor tab.
|
||||||
self._success_message = Message(
|
self._success_message = Message(
|
||||||
i18n_catalog.i18nc("@info:status", "Print job was successfully sent to the printer."),
|
i18n_catalog.i18nc("@info:status", "Print job was successfully sent to the printer."),
|
||||||
lifetime=5, dismissable=True,
|
lifetime=5, dismissable=True,
|
||||||
@ -329,7 +354,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||||||
|
|
||||||
@pyqtProperty("QVariantList", notify = printJobsChanged)
|
@pyqtProperty("QVariantList", notify = printJobsChanged)
|
||||||
def queuedPrintJobs(self) -> List[PrintJobOutputModel]:
|
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)
|
@pyqtProperty("QVariantList", notify = printJobsChanged)
|
||||||
def activePrintJobs(self) -> List[PrintJobOutputModel]:
|
def activePrintJobs(self) -> List[PrintJobOutputModel]:
|
||||||
@ -348,6 +373,10 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||||||
result.append({"machine_type": machine_type, "count": str(printer_count[machine_type])})
|
result.append({"machine_type": machine_type, "count": str(printer_count[machine_type])})
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@pyqtProperty("QVariantList", notify=clusterPrintersChanged)
|
||||||
|
def printers(self):
|
||||||
|
return self._printers
|
||||||
|
|
||||||
@pyqtSlot(int, result = str)
|
@pyqtSlot(int, result = str)
|
||||||
def formatDuration(self, seconds: int) -> str:
|
def formatDuration(self, seconds: int) -> str:
|
||||||
return Duration(seconds).getDisplayString(DurationFormat.Format.Short)
|
return Duration(seconds).getDisplayString(DurationFormat.Format.Short)
|
||||||
@ -364,6 +393,19 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||||||
datetime_completed = datetime.fromtimestamp(current_time + time_remaining)
|
datetime_completed = datetime.fromtimestamp(current_time + time_remaining)
|
||||||
return (datetime_completed.strftime("%a %b ") + "{day}".format(day=datetime_completed.day)).upper()
|
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:
|
def _printJobStateChanged(self) -> None:
|
||||||
username = self._getUserName()
|
username = self._getUserName()
|
||||||
|
|
||||||
@ -392,11 +434,26 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||||||
super().connect()
|
super().connect()
|
||||||
self.sendMaterialProfiles()
|
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:
|
def _update(self) -> None:
|
||||||
super()._update()
|
super()._update()
|
||||||
self.get("printers/", on_finished = self._onGetPrintersDataFinished)
|
self.get("printers/", on_finished = self._onGetPrintersDataFinished)
|
||||||
self.get("print_jobs/", on_finished = self._onGetPrintJobsFinished)
|
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:
|
def _onGetPrintJobsFinished(self, reply: QNetworkReply) -> None:
|
||||||
if not checkValidGetReply(reply):
|
if not checkValidGetReply(reply):
|
||||||
return
|
return
|
||||||
@ -407,16 +464,19 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||||||
|
|
||||||
print_jobs_seen = []
|
print_jobs_seen = []
|
||||||
job_list_changed = False
|
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"])
|
print_job = findByKey(self._print_jobs, print_job_data["uuid"])
|
||||||
|
|
||||||
if print_job is None:
|
if print_job is None:
|
||||||
print_job = self._createPrintJobModel(print_job_data)
|
print_job = self._createPrintJobModel(print_job_data)
|
||||||
job_list_changed = True
|
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)
|
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"]:
|
if print_job.state in ["failed", "finished", "aborted", "none"]:
|
||||||
# Print job was already completed, so don't attach it to a printer.
|
# Print job was already completed, so don't attach it to a printer.
|
||||||
printer = None
|
printer = None
|
||||||
@ -437,6 +497,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||||||
job_list_changed = job_list_changed or self._removeJob(removed_job)
|
job_list_changed = job_list_changed or self._removeJob(removed_job)
|
||||||
|
|
||||||
if job_list_changed:
|
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.
|
self.printJobsChanged.emit() # Do a single emit for all print job changes.
|
||||||
|
|
||||||
def _onGetPrintersDataFinished(self, reply: QNetworkReply) -> None:
|
def _onGetPrintersDataFinished(self, reply: QNetworkReply) -> None:
|
||||||
@ -478,16 +540,59 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||||||
def _createPrintJobModel(self, data: Dict[str, Any]) -> PrintJobOutputModel:
|
def _createPrintJobModel(self, data: Dict[str, Any]) -> PrintJobOutputModel:
|
||||||
print_job = PrintJobOutputModel(output_controller=ClusterUM3PrinterOutputController(self),
|
print_job = PrintJobOutputModel(output_controller=ClusterUM3PrinterOutputController(self),
|
||||||
key=data["uuid"], name= data["name"])
|
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)
|
print_job.stateChanged.connect(self._printJobStateChanged)
|
||||||
self._print_jobs.append(print_job)
|
|
||||||
return print_job
|
return print_job
|
||||||
|
|
||||||
def _updatePrintJob(self, print_job: PrintJobOutputModel, data: Dict[str, Any]) -> None:
|
def _updatePrintJob(self, print_job: PrintJobOutputModel, data: Dict[str, Any]) -> None:
|
||||||
print_job.updateTimeTotal(data["time_total"])
|
print_job.updateTimeTotal(data["time_total"])
|
||||||
print_job.updateTimeElapsed(data["time_elapsed"])
|
print_job.updateTimeElapsed(data["time_elapsed"])
|
||||||
print_job.updateState(data["status"])
|
impediments_to_printing = data.get("impediments_to_printing", [])
|
||||||
print_job.updateOwner(data["owner"])
|
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:
|
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.
|
# 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.
|
# 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"]
|
material_data = extruder_data["material"]
|
||||||
if extruder.activeMaterial is None or extruder.activeMaterial.guid != material_data["guid"]:
|
if extruder.activeMaterial is None or extruder.activeMaterial.guid != material_data["guid"]:
|
||||||
containers = ContainerRegistry.getInstance().findInstanceContainers(type="material",
|
material = self._createMaterialOutputModel(material_data)
|
||||||
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)
|
|
||||||
extruder.updateActiveMaterial(material)
|
extruder.updateActiveMaterial(material)
|
||||||
|
|
||||||
def _removeJob(self, job: PrintJobOutputModel) -> bool:
|
def _removeJob(self, job: PrintJobOutputModel) -> bool:
|
||||||
@ -568,6 +656,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||||||
job = SendMaterialJob(device = self)
|
job = SendMaterialJob(device = self)
|
||||||
job.run()
|
job.run()
|
||||||
|
|
||||||
|
|
||||||
def loadJsonFromReply(reply: QNetworkReply) -> Optional[List[Dict[str, Any]]]:
|
def loadJsonFromReply(reply: QNetworkReply) -> Optional[List[Dict[str, Any]]]:
|
||||||
try:
|
try:
|
||||||
result = json.loads(bytes(reply.readAll()).decode("utf-8"))
|
result = json.loads(bytes(reply.readAll()).decode("utf-8"))
|
||||||
@ -586,8 +675,8 @@ def checkValidGetReply(reply: QNetworkReply) -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def findByKey(list: List[Union[PrintJobOutputModel, PrinterOutputModel]], key: str) -> Optional[PrintJobOutputModel]:
|
def findByKey(lst: List[Union[PrintJobOutputModel, PrinterOutputModel]], key: str) -> Optional[PrintJobOutputModel]:
|
||||||
for item in list:
|
for item in lst:
|
||||||
if item.key == key:
|
if item.key == key:
|
||||||
return item
|
return item
|
||||||
return None
|
return None
|
||||||
|
@ -6,8 +6,6 @@ from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
|
|||||||
MYPY = False
|
MYPY = False
|
||||||
if MYPY:
|
if MYPY:
|
||||||
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
||||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
|
||||||
|
|
||||||
|
|
||||||
class ClusterUM3PrinterOutputController(PrinterOutputController):
|
class ClusterUM3PrinterOutputController(PrinterOutputController):
|
||||||
def __init__(self, output_device):
|
def __init__(self, output_device):
|
||||||
|
@ -12,22 +12,82 @@ Item
|
|||||||
|
|
||||||
width: Math.round(parent.width / 2)
|
width: Math.round(parent.width / 2)
|
||||||
height: childrenRect.height
|
height: childrenRect.height
|
||||||
Label
|
|
||||||
|
Item
|
||||||
{
|
{
|
||||||
id: materialLabel
|
id: extruderCircle
|
||||||
text: printCoreConfiguration.activeMaterial != null ? printCoreConfiguration.activeMaterial.name : ""
|
width: 30
|
||||||
elide: Text.ElideRight
|
height: 30
|
||||||
width: parent.width
|
|
||||||
font: UM.Theme.getFont("very_small")
|
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
|
id: printAndMaterialLabel
|
||||||
text: printCoreConfiguration.hotendID
|
anchors
|
||||||
anchors.top: materialLabel.bottom
|
{
|
||||||
elide: Text.ElideRight
|
right: parent.right
|
||||||
width: parent.width
|
left: extruderCircle.right
|
||||||
font: UM.Theme.getFont("very_small")
|
margins: UM.Theme.getSize("default_margin").width
|
||||||
opacity: 0.5
|
}
|
||||||
|
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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
378
plugins/UM3NetworkPrinting/PrintJobInfoBlock.qml
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
plugins/UM3NetworkPrinting/PrinterFamilyPill.qml
Normal file
@ -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: ""
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,8 @@ import UM 1.3 as UM
|
|||||||
|
|
||||||
Item
|
Item
|
||||||
{
|
{
|
||||||
|
property var camera: null
|
||||||
|
|
||||||
Rectangle
|
Rectangle
|
||||||
{
|
{
|
||||||
anchors.fill:parent
|
anchors.fill:parent
|
||||||
@ -17,7 +19,7 @@ Item
|
|||||||
MouseArea
|
MouseArea
|
||||||
{
|
{
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
onClicked: OutputDevice.setActivePrinter(null)
|
onClicked: OutputDevice.setActiveCamera(null)
|
||||||
z: 0
|
z: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,7 +34,7 @@ Item
|
|||||||
width: 20 * screenScaleFactor
|
width: 20 * screenScaleFactor
|
||||||
height: 20 * screenScaleFactor
|
height: 20 * screenScaleFactor
|
||||||
|
|
||||||
onClicked: OutputDevice.setActivePrinter(null)
|
onClicked: OutputDevice.setActiveCamera(null)
|
||||||
|
|
||||||
style: ButtonStyle
|
style: ButtonStyle
|
||||||
{
|
{
|
||||||
@ -65,23 +67,24 @@ Item
|
|||||||
{
|
{
|
||||||
if(visible)
|
if(visible)
|
||||||
{
|
{
|
||||||
if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null)
|
if(camera != null)
|
||||||
{
|
{
|
||||||
OutputDevice.activePrinter.camera.start()
|
camera.start()
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null)
|
if(camera != null)
|
||||||
{
|
{
|
||||||
OutputDevice.activePrinter.camera.stop()
|
camera.stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
source:
|
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 "";
|
return "";
|
||||||
}
|
}
|
||||||
@ -92,7 +95,7 @@ Item
|
|||||||
anchors.fill: cameraImage
|
anchors.fill: cameraImage
|
||||||
onClicked:
|
onClicked:
|
||||||
{
|
{
|
||||||
OutputDevice.setActivePrinter(null)
|
OutputDevice.setActiveCamera(null)
|
||||||
}
|
}
|
||||||
z: 1
|
z: 1
|
||||||
}
|
}
|
||||||
|
1
plugins/UM3NetworkPrinting/UM3-icon.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 46.16 48"><defs><style>.cls-1{fill:#9a9a9a;}</style></defs><title>UM3-icon</title><g id="Symbols"><g id="system_overview_inactive" data-name="system overview inactive"><path id="Shape" class="cls-1" d="M18.4,12.2h9.26c.1,0,.1,0,.2-.2l1.73-4.27a.22.22,0,0,0-.2-.2H16.67a.22.22,0,0,0-.2.2L18.2,12C18.3,12.2,18.3,12.2,18.4,12.2Z"/><path id="Shape-2" data-name="Shape" class="cls-1" d="M38.33,35.08H7.72a.48.48,0,0,0-.5.51V37a.48.48,0,0,0,.5.51H38.44a.48.48,0,0,0,.5-.51V35.59A.64.64,0,0,0,38.33,35.08Z"/><path id="Shape-3" data-name="Shape" class="cls-1" d="M0,0V48H3.76a2.86,2.86,0,0,1,2.13-1H40.27a2.86,2.86,0,0,1,2.13,1h3.76V0ZM41.28,37a2.83,2.83,0,0,1-2.84,2.84H7.72A2.84,2.84,0,0,1,4.88,37V5.49a.65.65,0,0,1,.61-.61H40.67a.65.65,0,0,1,.61.61Z"/></g></g></svg>
|
After Width: | Height: | Size: 847 B |
1
plugins/UM3NetworkPrinting/UM3x-icon.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 46.16 58"><defs><style>.cls-1{fill:#9a9a9a;}</style></defs><title>UM3x-icon</title><g id="Symbols"><g id="system_overview_inactive" data-name="system overview inactive"><path id="Shape" class="cls-1" d="M18.4,12.2h9.26c.1,0,.1,0,.2-.2l1.73-4.27a.22.22,0,0,0-.2-.2H16.67a.22.22,0,0,0-.2.2L18.2,12C18.3,12.2,18.3,12.2,18.4,12.2Z"/><path id="Shape-2" data-name="Shape" class="cls-1" d="M38.33,45.08H7.72a.48.48,0,0,0-.5.51V47a.48.48,0,0,0,.5.51H38.44a.48.48,0,0,0,.5-.51V45.59A.64.64,0,0,0,38.33,45.08Z"/><path id="Shape-3" data-name="Shape" class="cls-1" d="M0,0V58H3.76a2.86,2.86,0,0,1,2.13-1H40.27a2.86,2.86,0,0,1,2.13,1h3.76V0ZM41.28,35.32V47a2.83,2.83,0,0,1-2.84,2.84H7.72A2.84,2.84,0,0,1,4.88,47V5.49a.65.65,0,0,1,.61-.61H40.67a.65.65,0,0,1,.61.61Z"/></g></g></svg>
|
After Width: | Height: | Size: 854 B |
1
plugins/UM3NetworkPrinting/UMs5-icon.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 58 58"><defs><style>.cls-1{fill:none;}.cls-2{fill:#9a9a9a;}</style></defs><title>UMs5-icon</title><path class="cls-1" d="M33.83,12.33c-.1.2-.1.2-.2.2H24.37c-.1,0-.1,0-.2-.2L22.44,8.06a.22.22,0,0,1,.2-.2H35.36a.22.22,0,0,1,.2.2Z"/><path class="cls-2" d="M35.36,7.86H22.64a.22.22,0,0,0-.2.2l1.73,4.27c.1.2.1.2.2.2h9.26c.1,0,.1,0,.2-.2l1.73-4.27A.22.22,0,0,0,35.36,7.86Z"/><path class="cls-2" d="M0,0V58H3.75a2.85,2.85,0,0,1,2.12-1H52.13a2.85,2.85,0,0,1,2.12,1H58V0ZM37.5,53.82a1.5,1.5,0,0,1-1.5,1.5H22a1.5,1.5,0,0,1-1.5-1.5v-4a1.5,1.5,0,0,1,1.5-1.5H36a1.5,1.5,0,0,1,1.5,1.5Zm15.63-18.5V47a2.83,2.83,0,0,1-2.83,2.84H38.5v0a2.5,2.5,0,0,0-2.5-2.5H22a2.5,2.5,0,0,0-2.5,2.5v0H7.7A2.83,2.83,0,0,1,4.87,47V5.49a.65.65,0,0,1,.6-.61H52.53a.65.65,0,0,1,.6.61Z"/></svg>
|
After Width: | Height: | Size: 842 B |
1
plugins/UM3NetworkPrinting/aborted-icon.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><title>aborted-icon</title><path d="M16,0A16,16,0,1,0,32,16,16,16,0,0,0,16,0Zm1.69,28.89a13,13,0,1,1,11.2-11.2A13,13,0,0,1,17.69,28.89Z"/><polygon points="20.6 9.28 16 13.88 11.4 9.28 9.28 11.4 13.88 16 9.28 20.6 11.4 22.72 16 18.12 20.6 22.72 22.72 20.6 18.12 16 22.72 11.4 20.6 9.28"/></svg>
|
After Width: | Height: | Size: 386 B |
1
plugins/UM3NetworkPrinting/approved-icon.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><title>approved-icon</title><path d="M16,29A13,13,0,1,0,3,16,13,13,0,0,0,16,29ZM8,14.59l6,5.3L23.89,9l2.22,2L14.18,24.11,6,16.83Z" fill="none"/><path d="M16,32A16,16,0,1,0,0,16,16,16,0,0,0,16,32ZM16,3A13,13,0,1,1,3,16,13,13,0,0,1,16,3Z"/><polygon points="26.11 11.01 23.89 8.99 13.96 19.89 8 14.59 6 16.83 14.18 24.11 26.11 11.01"/></svg>
|
After Width: | Height: | Size: 431 B |
1
plugins/UM3NetworkPrinting/paused-icon.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><title>paused-icon</title><path d="M16,0A16,16,0,1,0,32,16,16,16,0,0,0,16,0Zm0,29A13,13,0,1,1,29,16,13,13,0,0,1,16,29Z"/><rect x="11.5" y="9" width="3" height="14"/><rect x="17.5" y="9" width="3" height="14"/></svg>
|
After Width: | Height: | Size: 308 B |
1
plugins/UM3NetworkPrinting/warning-icon.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><title>warning-icon</title><path d="M18.09,1.31A2.35,2.35,0,0,0,16,0a2.31,2.31,0,0,0-2.09,1.31L.27,28.44A2.49,2.49,0,0,0,.11,30.3a2.38,2.38,0,0,0,1.16,1.42A2.33,2.33,0,0,0,2.36,32H29.64A2.4,2.4,0,0,0,32,29.57a2.55,2.55,0,0,0-.27-1.14ZM3.34,29,16,3.83,28.66,29Z"/><polygon points="13.94 25.19 13.94 25.19 13.94 25.19 13.94 25.19"/><polygon points="14.39 21.68 17.61 21.68 18.11 11.85 13.89 11.85 14.39 21.68"/><path d="M16.06,23.08a2.19,2.19,0,0,0-1.56,3.66,2.14,2.14,0,0,0,1.56.55,2.06,2.06,0,0,0,1.54-.55,2.1,2.1,0,0,0,.55-1.55,2.17,2.17,0,0,0-.53-1.55A2.05,2.05,0,0,0,16.06,23.08Z"/></svg>
|
After Width: | Height: | Size: 684 B |
@ -274,7 +274,7 @@ Column
|
|||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
|
|
||||||
// Everthing for the extruder icon
|
// Everything for the extruder icon
|
||||||
Item
|
Item
|
||||||
{
|
{
|
||||||
id: extruderIconItem
|
id: extruderIconItem
|
||||||
|