diff --git a/cura/PrinterOutput/PrintJobOutputModel.py b/cura/PrinterOutput/PrintJobOutputModel.py
index 7366b95f86..c194f5df32 100644
--- a/cura/PrinterOutput/PrintJobOutputModel.py
+++ b/cura/PrinterOutput/PrintJobOutputModel.py
@@ -12,6 +12,8 @@ if TYPE_CHECKING:
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
+from cura.PrinterOutput.ConfigurationChangeModel import ConfigurationChangeModel
+
class PrintJobOutputModel(QObject):
stateChanged = pyqtSignal()
@@ -24,6 +26,7 @@ class PrintJobOutputModel(QObject):
configurationChanged = pyqtSignal()
previewImageChanged = pyqtSignal()
compatibleMachineFamiliesChanged = pyqtSignal()
+ configurationChangesChanged = pyqtSignal()
def __init__(self, output_controller: "PrinterOutputController", key: str = "", name: str = "", parent=None) -> None:
super().__init__(parent)
@@ -41,6 +44,7 @@ class PrintJobOutputModel(QObject):
self._preview_image_id = 0
self._preview_image = None # type: Optional[QImage]
+ self._configuration_changes = [] # type: List[ConfigurationChangeModel]
@pyqtProperty("QStringList", notify=compatibleMachineFamiliesChanged)
def compatibleMachineFamilies(self):
@@ -147,4 +151,14 @@ class PrintJobOutputModel(QObject):
@pyqtSlot(str)
def setState(self, state):
- self._output_controller.setJobState(self, state)
\ No newline at end of file
+ self._output_controller.setJobState(self, state)
+
+ @pyqtProperty("QVariantList", notify=configurationChangesChanged)
+ def configurationChanges(self) -> List[ConfigurationChangeModel]:
+ return self._configuration_changes
+
+ def updateConfigurationChanges(self, changes: List[ConfigurationChangeModel]) -> None:
+ if len(self._configuration_changes) == 0 and len(changes) == 0:
+ return
+ self._configuration_changes = changes
+ self.configurationChangesChanged.emit()
diff --git a/plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml b/plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml
index f6cf6607c7..b55b5c6779 100644
--- a/plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml
+++ b/plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml
@@ -85,7 +85,6 @@ Component
anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("default_margin").height
anchors.leftMargin: UM.Theme.getSize("default_margin").height
- height: 175 * screenScaleFactor
}
}
}
diff --git a/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml b/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml
index 71c2104318..f0e07807e2 100644
--- a/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml
+++ b/plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml
@@ -2,6 +2,8 @@ import QtQuick 2.2
import QtQuick.Controls 2.0
import QtQuick.Controls.Styles 1.4
import QtGraphicalEffects 1.0
+import QtQuick.Layouts 1.1
+import QtQuick.Dialogs 1.1
import UM 1.3 as UM
@@ -9,6 +11,110 @@ import UM 1.3 as UM
Item
{
id: base
+
+ function haveAlert() {
+ return printJob.configurationChanges.length !== 0;
+ }
+
+ function alertHeight() {
+ return haveAlert() ? 230 : 0;
+ }
+
+ function alertText() {
+ if (printJob.configurationChanges.length === 0) {
+ return "";
+ }
+
+ var topLine;
+ if (materialsAreKnown(printJob)) {
+ topLine = catalog.i18nc("@label", "The assigned printer, %1, requires the following configuration change(s):").arg(printJob.assignedPrinter.name);
+ } else {
+ topLine = catalog.i18nc("@label", "The printer %1 is assigned, but the job contains an unknown material configuration.").arg(printJob.assignedPrinter.name);
+ }
+ var result = "
" + topLine +"
";
+
+ for (var i=0; i" + text + "";
+ }
+ return result;
+ }
+
+ function materialsAreKnown(printJob) {
+ var conf0 = printJob.configuration[0];
+ if (conf0 && !conf0.material.material) {
+ return false;
+ }
+
+ var conf1 = printJob.configuration[1];
+ if (conf1 && !conf1.material.material) {
+ return false;
+ }
+
+ return true;
+ }
+
+ function formatBuildPlateType(buildPlateType) {
+ var translationText = "";
+
+ switch (buildPlateType) {
+ case 'glass':
+ translationText = catalog.i18nc("@label", "Glass");
+ break;
+ case 'aluminum':
+ translationText = catalog.i18nc("@label", "Aluminum");
+ break;
+ default:
+ translationText = null;
+ }
+ return translationText;
+ }
+
+ function formatPrintJobName(name) {
+ var extensionsToRemove = [
+ '.gz',
+ '.gcode',
+ '.ufp'
+ ];
+
+ for (var i = 0; i < extensionsToRemove.length; i++) {
+ var extension = extensionsToRemove[i];
+
+ if (name.slice(-extension.length) === extension) {
+ name = name.substring(0, name.length - extension.length);
+ }
+ }
+
+ return name;
+ }
+
+ function isPrintJobForcable(printJob) {
+ var forcable = true;
+
+ for (var i = 0; i < printJob.configurationChanges.length; i++) {
+ var typeOfChange = printJob.configurationChanges[i].typeOfChange;
+ if (typeOfChange === 'material_insert' || typeOfChange === 'buildplate_change') {
+ forcable = false;
+ }
+ }
+
+ return forcable;
+ }
+
+ property var cardHeight: 175
+
+ height: (cardHeight + alertHeight()) * screenScaleFactor
property var printJob: null
property var shadowRadius: 5 * screenScaleFactor
function getPrettyTime(time)
@@ -27,6 +133,9 @@ Item
Rectangle
{
id: background
+
+ height: (cardHeight + alertHeight()) * screenScaleFactor
+
anchors
{
top: parent.top
@@ -35,7 +144,7 @@ Item
leftMargin: base.shadowRadius
rightMargin: base.shadowRadius
right: parent.right
- bottom: parent.bottom
+ //bottom: parent.bottom - alertHeight() * screenScaleFactor
bottomMargin: base.shadowRadius
}
@@ -47,6 +156,18 @@ Item
color: "#3F000000" // 25% shadow
}
+ Rectangle
+ {
+ height: cardHeight * screenScaleFactor
+
+ anchors
+ {
+ top: parent.top
+ left: parent.left
+ right: parent.right
+ // bottom: parent.bottom
+ }
+
Item
{
// Content on the left of the infobox
@@ -392,15 +513,128 @@ Item
}
}
-
+ }
Rectangle
{
+ height: cardHeight * screenScaleFactor
color: UM.Theme.getColor("viewport_background")
width: 2 * screenScaleFactor
anchors.top: parent.top
- anchors.bottom: parent.bottom
anchors.margins: UM.Theme.getSize("default_margin").height
anchors.horizontalCenter: parent.horizontalCenter
}
+
+ // Alert / Configuration change box
+ Rectangle
+ {
+ height: alertHeight() * screenScaleFactor
+
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+
+color: "#ff00ff"
+ ColumnLayout
+ {
+ anchors.fill: parent
+ RowLayout
+ {
+ Item
+ {
+ Layout.fillWidth: true
+ }
+
+ Label
+ {
+ font: UM.Theme.getFont("default_bold")
+ text: "Configuration change"
+ }
+
+ UM.RecolorImage
+ {
+ id: collapseIcon
+ width: 15
+ height: 15
+ sourceSize.width: width
+ sourceSize.height: height
+
+ // FIXME
+ source: base.collapsed ? UM.Theme.getIcon("arrow_left") : UM.Theme.getIcon("arrow_bottom")
+ color: "black"
+ }
+
+ Item
+ {
+ Layout.fillWidth: true
+ }
+
+ }
+
+ Rectangle
+ {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+color: "red"
+
+ Rectangle
+ {
+color: "green"
+ width: childrenRect.width
+
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+
+ ColumnLayout
+ {
+ width: childrenRect.width
+
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+
+ Text
+ {
+ Layout.alignment: Qt.AlignTop
+
+ textFormat: Text.StyledText
+ font: UM.Theme.getFont("default_bold")
+ text: alertText()
+ }
+
+ Button
+ {
+ visible: isPrintJobForcable(printJob)
+ text: catalog.i18nc("@label", "Override")
+ onClicked: {
+ overrideConfirmationDialog.visible = true;
+ }
+ }
+
+ // Spacer
+ Item
+ {
+ Layout.fillHeight: true
+ }
+ }
+ }
+ }
+ }
+ }
+
+ MessageDialog
+ {
+ id: overrideConfirmationDialog
+ title: catalog.i18nc("@window:title", "Override configuration")
+ icon: StandardIcon.Warning
+ text: {
+ var printJobName = formatPrintJobName(printJob.name);
+ var confirmText = catalog.i18nc("@label", "Starting a print job with an incompatible configuration could damage your 3D printer. Are you sure you want to override the configuration and print %1?").arg(printJobName);
+ return confirmText;
+ }
+
+ standardButtons: StandardButton.Yes | StandardButton.No
+ Component.onCompleted: visible = false
+ onYes: OutputDevice.forceSendJob(printJob.key)
+ }
}
}
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py b/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py
index 409ca7a84a..79040373ae 100644
--- a/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py
+++ b/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py
@@ -17,6 +17,7 @@ from UM.Scene.SceneNode import SceneNode # For typing.
from UM.Version import Version # To check against firmware versions for support.
from cura.CuraApplication import CuraApplication
+from cura.PrinterOutput.ConfigurationChangeModel import ConfigurationChangeModel
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel
from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState
@@ -406,6 +407,11 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
# 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)
+ @pyqtSlot(str)
+ def forceSendJob(self, print_job_uuid: str) -> None:
+ data = "{\"force\": true}"
+ self.put("print_jobs/{uuid}".format(uuid=print_job_uuid), data, on_finished=None)
+
def _printJobStateChanged(self) -> None:
username = self._getUserName()
@@ -574,6 +580,16 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
if not status_set_by_impediment:
print_job.updateState(data["status"])
+ print_job.updateConfigurationChanges(self._createConfigurationChanges(data["configuration_changes_required"]))
+
+ def _createConfigurationChanges(self, data: List[Dict[str, Any]]) -> List[ConfigurationChangeModel]:
+ result = []
+ for change in data:
+ result.append(ConfigurationChangeModel(type_of_change=change["type_of_change"],
+ index=change["index"],
+ target_name=change["target_name"],
+ origin_name=change["origin_name"]))
+ return result
def _createMaterialOutputModel(self, material_data) -> MaterialOutputModel:
containers = ContainerRegistry.getInstance().findInstanceContainers(type="material", GUID=material_data["guid"])