Merge branch '3.0' into fix_infill_slider_rounding

This commit is contained in:
Diego Prado Gesto 2017-09-27 16:11:10 +02:00
commit c03d54f598
37 changed files with 1901 additions and 170 deletions

View File

@ -10,7 +10,6 @@ from PyQt5.QtWidgets import QSplashScreen
from UM.Resources import Resources
from UM.Application import Application
class CuraSplashScreen(QSplashScreen):
def __init__(self):
super().__init__()
@ -61,7 +60,7 @@ class CuraSplashScreen(QSplashScreen):
# draw version text
font = QFont() # Using system-default font here
font.setPointSize(28)
font.setPixelSize(37)
painter.setFont(font)
painter.drawText(220, 66, 330 * self._scale, 230 * self._scale, Qt.AlignLeft | Qt.AlignTop, version[0])
if len(version) > 1:
@ -81,7 +80,7 @@ class CuraSplashScreen(QSplashScreen):
# draw message text
if self._current_message:
font = QFont() # Using system-default font here
font.setPointSize(10)
font.setPixelSize(13)
pen = QPen()
pen.setColor(QColor(255, 255, 255, 255))
painter.setPen(pen)
@ -107,5 +106,3 @@ class CuraSplashScreen(QSplashScreen):
self._to_stop = True
self._change_timer.stop()
super().close()

View File

@ -12,11 +12,13 @@ UM.Dialog
{
title: catalog.i18nc("@title:window", "Open Project")
width: 500
height: 400
minimumWidth: 500 * screenScaleFactor
minimumHeight: 400 * screenScaleFactor
width: minimumWidth
height: minumumHeight
property int comboboxHeight: 15
property int spacerHeight: 10
property int comboboxHeight: 15 * screenScaleFactor
property int spacerHeight: 10 * screenScaleFactor
onClosing: manager.notifyClosed()
onVisibleChanged:
@ -31,7 +33,7 @@ UM.Dialog
Item
{
anchors.fill: parent
anchors.margins: 20
anchors.margins: 20 * screenScaleFactor
UM.I18nCatalog
{
@ -59,7 +61,7 @@ UM.Dialog
Column
{
anchors.fill: parent
spacing: 2
spacing: 2 * screenScaleFactor
Label
{
id: titleLabel
@ -373,7 +375,7 @@ UM.Dialog
enabled: true
anchors.bottom: parent.bottom
anchors.right: ok_button.left
anchors.rightMargin:2
anchors.rightMargin: 2 * screenScaleFactor
}
Button
{

View File

@ -10,11 +10,11 @@ import UM 1.1 as UM
UM.Dialog
{
width: 350 * Screen.devicePixelRatio;
minimumWidth: 350 * Screen.devicePixelRatio;
width: minimumWidth;
minimumWidth: 350 * screenScaleFactor;
height: 250 * Screen.devicePixelRatio;
minimumHeight: 250 * Screen.devicePixelRatio;
height: minimumHeight;
minimumHeight: 250 * screenScaleFactor;
title: catalog.i18nc("@title:window", "Convert Image...")
@ -23,8 +23,8 @@ UM.Dialog
UM.I18nCatalog{id: catalog; name:"cura"}
anchors.fill: parent;
Layout.fillWidth: true
columnSpacing: 16
rowSpacing: 4
columnSpacing: 16 * screenScaleFactor
rowSpacing: 4 * screenScaleFactor
columns: 1
UM.TooltipArea {
@ -36,7 +36,7 @@ UM.Dialog
Label {
text: catalog.i18nc("@action:label","Height (mm)")
width: 150
width: 150 * screenScaleFactor
anchors.verticalCenter: parent.verticalCenter
}
@ -44,7 +44,7 @@ UM.Dialog
id: peak_height
objectName: "Peak_Height"
validator: DoubleValidator {notation: DoubleValidator.StandardNotation; bottom: -500; top: 500;}
width: 180
width: 180 * screenScaleFactor
onTextChanged: { manager.onPeakHeightChanged(text) }
}
}
@ -59,7 +59,7 @@ UM.Dialog
Label {
text: catalog.i18nc("@action:label","Base (mm)")
width: 150
width: 150 * screenScaleFactor
anchors.verticalCenter: parent.verticalCenter
}
@ -67,7 +67,7 @@ UM.Dialog
id: base_height
objectName: "Base_Height"
validator: DoubleValidator {notation: DoubleValidator.StandardNotation; bottom: 0; top: 500;}
width: 180
width: 180 * screenScaleFactor
onTextChanged: { manager.onBaseHeightChanged(text) }
}
}
@ -82,7 +82,7 @@ UM.Dialog
Label {
text: catalog.i18nc("@action:label","Width (mm)")
width: 150
width: 150 * screenScaleFactor
anchors.verticalCenter: parent.verticalCenter
}
@ -91,7 +91,7 @@ UM.Dialog
objectName: "Width"
focus: true
validator: DoubleValidator {notation: DoubleValidator.StandardNotation; bottom: 1; top: 500;}
width: 180
width: 180 * screenScaleFactor
onTextChanged: { manager.onWidthChanged(text) }
}
}
@ -106,7 +106,7 @@ UM.Dialog
Label {
text: catalog.i18nc("@action:label","Depth (mm)")
width: 150
width: 150 * screenScaleFactor
anchors.verticalCenter: parent.verticalCenter
}
TextField {
@ -114,7 +114,7 @@ UM.Dialog
objectName: "Depth"
focus: true
validator: DoubleValidator {notation: DoubleValidator.StandardNotation; bottom: 1; top: 500;}
width: 180
width: 180 * screenScaleFactor
onTextChanged: { manager.onDepthChanged(text) }
}
}
@ -130,14 +130,14 @@ UM.Dialog
//Empty label so 2 column layout works.
Label {
text: ""
width: 150
width: 150 * screenScaleFactor
anchors.verticalCenter: parent.verticalCenter
}
ComboBox {
id: image_color_invert
objectName: "Image_Color_Invert"
model: [ catalog.i18nc("@item:inlistbox","Lighter is higher"), catalog.i18nc("@item:inlistbox","Darker is higher") ]
width: 180
width: 180 * screenScaleFactor
onCurrentIndexChanged: { manager.onImageColorInvertChanged(currentIndex) }
}
}
@ -152,13 +152,13 @@ UM.Dialog
Label {
text: catalog.i18nc("@action:label","Smoothing")
width: 150
width: 150 * screenScaleFactor
anchors.verticalCenter: parent.verticalCenter
}
Item {
width: 180
height: 20
width: 180 * screenScaleFactor
height: 20 * screenScaleFactor
Layout.fillWidth: true
Slider {

View File

@ -111,7 +111,7 @@ Item
visible: !UM.LayerView.compatibilityMode
style: UM.Theme.styles.combobox
anchors.right: parent.right
anchors.rightMargin: 10
anchors.rightMargin: 10 * screenScaleFactor
onActivated:
{
@ -353,7 +353,7 @@ Item
property real minimumRangeHandleSize: UM.Theme.getSize("slider_handle").width / 2
property real trackThickness: UM.Theme.getSize("slider_groove").width
property real trackRadius: trackThickness / 2
property real trackBorderWidth: UM.Theme.getSize("default_lining").width / 2
property real trackBorderWidth: UM.Theme.getSize("default_lining").width
property color upperHandleColor: UM.Theme.getColor("slider_handle")
property color lowerHandleColor: UM.Theme.getColor("slider_handle")
property color rangeHandleColor: UM.Theme.getColor("slider_groove_fill")
@ -602,7 +602,7 @@ Item
anchors.leftMargin: UM.Theme.getSize("default_margin").width / 2;
anchors.verticalCenter: parent.verticalCenter;
width: Math.max(UM.Theme.getSize("line").width * maxValue.length + 2, 20);
width: Math.max(UM.Theme.getSize("line").width * maxValue.length + 2 * screenScaleFactor, 20 * screenScaleFactor);
style: TextFieldStyle
{
textColor: UM.Theme.getColor("setting_control_text");

View File

@ -112,7 +112,13 @@ class MachineSettingsAction(MachineAction):
if not self._global_container_stack:
return 0
return len(self._global_container_stack.getMetaDataEntry("machine_extruder_trains"))
# If there is a printer that originally is multi-extruder, it's not allowed to change the number of extruders
# It's just allowed in case of Custom FDM printers
definition_container = self._global_container_stack.getBottom()
if definition_container.getId() == "custom":
return len(self._global_container_stack.getMetaDataEntry("machine_extruder_trains"))
return 0
@pyqtSlot(int)
def setMachineExtruderCount(self, extruder_count):

View File

@ -30,7 +30,7 @@ Item {
{
// This is to ensure that the panel is first increasing in size up to 200 and then shows a scrollbar.
// It kinda looks ugly otherwise (big panel, no content on it)
property int maximumHeight: 200 * Screen.devicePixelRatio
property int maximumHeight: 200 * screenScaleFactor
height: Math.min(contents.count * (UM.Theme.getSize("section").height + UM.Theme.getSize("default_lining").height), maximumHeight)
ScrollView
@ -246,7 +246,7 @@ Item {
id: settingPickDialog
title: catalog.i18nc("@title:window", "Select Settings to Customize for this model")
width: Screen.devicePixelRatio * 360;
width: screenScaleFactor * 360;
property string labelFilter: ""

View File

@ -9,10 +9,10 @@ UM.Dialog
id: base
title: catalog.i18nc("@title:window", "Find & Update plugins")
width: 600 * Screen.devicePixelRatio
height: 450 * Screen.devicePixelRatio
minimumWidth: 350 * Screen.devicePixelRatio
minimumHeight: 350 * Screen.devicePixelRatio
width: 600 * screenScaleFactor
height: 450 * screenScaleFactor
minimumWidth: 350 * screenScaleFactor
minimumHeight: 350 * screenScaleFactor
Item
{
anchors.fill: parent

View File

@ -0,0 +1,243 @@
import QtQuick 2.2
import QtQuick.Controls 1.4
import UM 1.3 as UM
import Cura 1.0 as Cura
Component
{
Item
{
id: base
property var manager: Cura.MachineManager.printerOutputDevices[0]
anchors.fill: parent
property var lineColor: "#DCDCDC" // TODO: Should be linked to theme.
property var cornerRadius: 4 // TODO: Should be linked to theme.
visible: manager != null
UM.I18nCatalog
{
id: catalog
name: "cura"
}
Label
{
id: activePrintersLabel
font: UM.Theme.getFont("large")
anchors.horizontalCenter: parent.horizontalCenter
text: Cura.MachineManager.printerOutputDevices[0].name
}
Label
{
id: printerGroupLabel
anchors.top: activePrintersLabel.bottom
text: catalog.i18nc("@label", "PRINTER GROUP")
anchors.horizontalCenter: parent.horizontalCenter
font: UM.Theme.getFont("very_small")
opacity: 0.65
}
Rectangle
{
id: printJobArea
border.width: UM.Theme.getSize("default_lining").width
border.color: lineColor
anchors.top: printerGroupLabel.bottom
anchors.topMargin: UM.Theme.getSize("default_margin").height
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.right: parent.right
anchors.rightMargin:UM.Theme.getSize("default_margin").width
radius: cornerRadius
height: childrenRect.height
Item
{
id: printJobTitleBar
width: parent.width
height: printJobTitleLabel.height + 2 * UM.Theme.getSize("default_margin").height
Label
{
id: printJobTitleLabel
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.top: parent.top
anchors.topMargin: UM.Theme.getSize("default_margin").height
text: catalog.i18nc("@title", "Print jobs")
font: UM.Theme.getFont("default")
opacity: 0.75
}
Rectangle
{
anchors.bottom: parent.bottom
height: UM.Theme.getSize("default_lining").width
color: lineColor
width: parent.width
}
}
Column
{
id: printJobColumn
anchors.top: printJobTitleBar.bottom
anchors.topMargin: UM.Theme.getSize("default_margin").height
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
//TODO; It's probably nicer to do this with a dynamic data model instead of hardcoding this.
//But you know the drill; time constraints don't result in elegant code.
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.numJobsPrinting
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.numJobsQueued
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
}
}
Rectangle
{
id: printersArea
border.width: UM.Theme.getSize("default_lining").width
border.color: lineColor
anchors.top: printJobArea.bottom
anchors.topMargin: UM.Theme.getSize("default_margin").height
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.right: parent.right
anchors.rightMargin:UM.Theme.getSize("default_margin").width
radius: cornerRadius
height: childrenRect.height
Item
{
id: printersTitleBar
width: parent.width
height: printJobTitleLabel.height + 2 * UM.Theme.getSize("default_margin").height
Label
{
id: printersTitleLabel
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.top: parent.top
anchors.topMargin: UM.Theme.getSize("default_margin").height
text: catalog.i18nc("@label:title", "Printers")
font: UM.Theme.getFont("default")
opacity: 0.75
}
Rectangle
{
anchors.bottom: parent.bottom
height: UM.Theme.getSize("default_lining").width
color: lineColor
width: parent.width
}
}
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
{
model: manager.connectedPrintersTypeCount
Item
{
width: parent.width
height: childrenRect.height
opacity: 0.65
Label
{
text: modelData.machine_type
font: UM.Theme.getFont("very_small")
}
Label
{
text: modelData.count
font: UM.Theme.getFont("small")
anchors.right: parent.right
}
}
}
}
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
}
}
}
}

View File

@ -0,0 +1,108 @@
import QtQuick 2.2
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.3 as UM
import Cura 1.0 as Cura
Component
{
Rectangle
{
width: maximumWidth
height: maximumHeight
color: "#FFFFFF" // TODO; Should not be hardcoded.
property var emphasisColor: "#44c0ff" //TODO: should be linked to theme.
property var lineColor: "#DCDCDC" // TODO: Should be linked to theme.
property var cornerRadius: 4 // TODO: Should be linked to theme.
UM.I18nCatalog
{
id: catalog
name: "cura"
}
Label
{
id: activePrintersLabel
font: UM.Theme.getFont("large")
text:
{
if (OutputDevice.connectedPrinters.length == 0){
return 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)
} else {
return ""
}
}
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: UM.Theme.getSize("default_margin").height
visible: OutputDevice.connectedPrinters.length == 0
}
Item
{
anchors.topMargin: UM.Theme.getSize("default_margin").height
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
width: Math.min(800, maximumWidth)
height: children.height
visible: OutputDevice.connectedPrinters.length != 0
Label
{
id: addRemovePrintersLabel
anchors.right: parent.right
text: "Add / remove printers"
}
MouseArea
{
anchors.fill: addRemovePrintersLabel
onClicked: Cura.MachineManager.printerOutputDevices[0].openPrinterControlPanel()
}
}
ScrollView
{
id: printerScrollView
anchors.margins: UM.Theme.getSize("default_margin").width
anchors.top: activePrintersLabel.bottom
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_lining").width // To ensure border can be drawn.
anchors.rightMargin: UM.Theme.getSize("default_lining").width
anchors.right: parent.right
ListView
{
anchors.fill: parent
spacing: -UM.Theme.getSize("default_lining").height
model: OutputDevice.connectedPrinters
delegate: PrinterInfoBlock
{
printer: modelData
width: Math.min(800, maximumWidth)
height: 125
// Add a 1 pix margin, as the border is sometimes cut off otherwise.
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
PrinterVideoStream
{
visible: OutputDevice.selectedPrinterName != ""
anchors.fill:parent
}
}
}

View File

@ -272,6 +272,28 @@ Cura.MachineAction
text: base.selectedPrinter ? base.selectedPrinter.ipAddress : ""
}
}
Label
{
width: parent.width
wrapMode: Text.WordWrap
text:{
// The property cluster size does not exist for older UM3 devices.
if(base.selectedPrinter.clusterSize == null || base.selectedPrinter.clusterSize == 1)
{
return "";
}
else if (base.selectedPrinter.clusterSize === 0)
{
return catalog.i18nc("@label", "Cura Connect: This printer is not set up to host a group of connected Ultimaker 3 printers.");
}
else
{
return catalog.i18nc("@label", "Cura Connect: This printer is set up to host a group of %1 connected Ultimaker 3 printers".arg(base.selectedPrinter.clusterSize));
}
}
}
Label
{
width: parent.width
@ -298,8 +320,8 @@ Cura.MachineAction
title: catalog.i18nc("@title:window", "Printer Address")
minimumWidth: 400 * Screen.devicePixelRatio
minimumHeight: 120 * Screen.devicePixelRatio
minimumWidth: 400 * screenScaleFactor
minimumHeight: 120 * screenScaleFactor
width: minimumWidth
height: minimumHeight

View File

@ -0,0 +1,638 @@
import datetime
import getpass
import gzip
import json
import os
import os.path
import time
from enum import Enum
from PyQt5.QtNetwork import QNetworkRequest, QHttpPart, QHttpMultiPart
from PyQt5.QtCore import QUrl, QByteArray, pyqtSlot, pyqtProperty, QCoreApplication, QTimer, pyqtSignal, QObject
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkReply
from PyQt5.QtQml import QQmlComponent, QQmlContext
from UM.Application import Application
from UM.Logger import Logger
from UM.Message import Message
from UM.OutputDevice import OutputDeviceError
from UM.i18n import i18nCatalog
from . import NetworkPrinterOutputDevice
i18n_catalog = i18nCatalog("cura")
class OutputStage(Enum):
ready = 0
uploading = 2
class NetworkClusterPrinterOutputDevice(NetworkPrinterOutputDevice.NetworkPrinterOutputDevice):
printJobsChanged = pyqtSignal()
printersChanged = pyqtSignal()
selectedPrinterChanged = pyqtSignal()
def __init__(self, key, address, properties, api_prefix, plugin_path):
super().__init__(key, address, properties, api_prefix)
# Store the address of the master.
self._master_address = address
name_property = properties.get(b"name", b"")
if name_property:
name = name_property.decode("utf-8")
else:
name = key
self._plugin_path = plugin_path
self.setName(name)
description = i18n_catalog.i18nc("@action:button Preceded by 'Ready to'.", "Print over network")
self.setShortDescription(description)
self.setDescription(description)
self._stage = OutputStage.ready
host_override = os.environ.get("CLUSTER_OVERRIDE_HOST", "")
if host_override:
Logger.log(
"w",
"Environment variable CLUSTER_OVERRIDE_HOST is set to [%s], cluster hosts are now set to this host",
host_override)
self._host = "http://" + host_override
else:
self._host = "http://" + address
# is the same as in NetworkPrinterOutputDevicePlugin
self._cluster_api_version = "1"
self._cluster_api_prefix = "/cluster-api/v" + self._cluster_api_version + "/"
self._api_base_uri = self._host + self._cluster_api_prefix
self._file_name = None
self._progress_message = None
self._request = None
self._reply = None
# The main reason to keep the 'multipart' form data on the object
# is to prevent the Python GC from claiming it too early.
self._multipart = None
self._print_view = None
self._request_job = []
self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ClusterMonitorItem.qml")
self._control_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ClusterControlItem.qml")
self._print_jobs = []
self._print_job_by_printer_uuid = {}
self._print_job_by_uuid = {} # Print jobs by their own uuid
self._printers = []
self._printers_dict = {} # by unique_name
self._connected_printers_type_count = []
self._automatic_printer = {"unique_name": "", "friendly_name": "Automatic"} # empty unique_name IS automatic selection
self._selected_printer = self._automatic_printer
self._cluster_status_update_timer = QTimer()
self._cluster_status_update_timer.setInterval(5000)
self._cluster_status_update_timer.setSingleShot(False)
self._cluster_status_update_timer.timeout.connect(self._requestClusterStatus)
self._can_pause = False
self._can_abort = False
self._can_pre_heat_bed = False
self._cluster_size = int(properties.get(b"cluster_size", 0))
self._cleanupRequest()
#These are texts that are to be translated for future features.
temporary_translation = i18n_catalog.i18n("This printer is not set up to host a group of connected Ultimaker 3 printers.")
temporary_translation2 = i18n_catalog.i18nc("Count is number of printers.", "This printer is the host for a group of {count} connected Ultimaker 3 printers.").format(count = 3)
temporary_translation3 = i18n_catalog.i18n("{printer_name} has finished printing '{job_name}'. Please collect the print and confirm clearing the build plate.") #When finished.
temporary_translation4 = i18n_catalog.i18n("{printer_name} is reserved to print '{job_name}'. Please change the printer's configuration to match the job, for it to start printing.") #When configuration changed.
@pyqtProperty(QObject, notify=selectedPrinterChanged)
def controlItem(self):
# TODO: Probably not the nicest way to do this. This needs to be done better at some point in time.
if not self._control_component:
self._createControlViewFromQML()
name = self._selected_printer.get("friendly_name")
if name == self._automatic_printer.get("friendly_name") or name == "":
return self._control_item
# Let cura use the default.
return None
@pyqtSlot(int, result = str)
def getTimeCompleted(self, time_remaining):
current_time = time.time()
datetime_completed = datetime.datetime.fromtimestamp(current_time + time_remaining)
return "{hour:02d}:{minute:02d}".format(hour = datetime_completed.hour, minute = datetime_completed.minute)
@pyqtSlot(int, result = str)
def getDateCompleted(self, time_remaining):
current_time = time.time()
datetime_completed = datetime.datetime.fromtimestamp(current_time + time_remaining)
return (datetime_completed.strftime("%a %b ") + "{day}".format(day=datetime_completed.day)).upper()
@pyqtProperty(int, constant = True)
def clusterSize(self):
return self._cluster_size
@pyqtProperty(str, notify=selectedPrinterChanged)
def name(self):
# Show the name of the selected printer.
# This is not the nicest way to do this, but changes to the Cura UI are required otherwise.
name = self._selected_printer.get("friendly_name")
if name != self._automatic_printer.get("friendly_name"):
return name
# Return name of cluster master.
return self._properties.get(b"name", b"").decode("utf-8")
def connect(self):
super().connect()
self._cluster_status_update_timer.start()
def close(self):
super().close()
self._cluster_status_update_timer.stop()
def _requestClusterStatus(self):
# TODO: Handle timeout. We probably want to know if the cluster is still reachable or not.
url = QUrl(self._api_base_uri + "print_jobs/")
print_jobs_request = QNetworkRequest(url)
self._addUserAgentHeader(print_jobs_request)
self._manager.get(print_jobs_request)
# See _finishedPrintJobsRequest()
url = QUrl(self._api_base_uri + "printers/")
printers_request = QNetworkRequest(url)
self._addUserAgentHeader(printers_request)
self._manager.get(printers_request)
# See _finishedPrintersRequest()
def _finishedPrintJobsRequest(self, reply):
try:
json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
except json.decoder.JSONDecodeError:
Logger.log("w", "Received an invalid print job state message: Not valid JSON.")
return
self.setPrintJobs(json_data)
def _finishedPrintersRequest(self, reply):
try:
json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
except json.decoder.JSONDecodeError:
Logger.log("w", "Received an invalid print job state message: Not valid JSON.")
return
self.setPrinters(json_data)
def materialHotendChangedMessage(self, callback):
pass # Do nothing.
def _startCameraStream(self):
## Request new image
url = QUrl("http://" + self._printers_dict[self._selected_printer["unique_name"]]["ip_address"] + ":8080/?action=stream")
self._image_request = QNetworkRequest(url)
self._addUserAgentHeader(self._image_request)
self._image_reply = self._manager.get(self._image_request)
self._image_reply.downloadProgress.connect(self._onStreamDownloadProgress)
def spawnPrintView(self):
if self._print_view is None:
path = QUrl.fromLocalFile(os.path.join(self._plugin_path, "PrintWindow.qml"))
component = QQmlComponent(Application.getInstance()._engine, path)
self._print_context = QQmlContext(Application.getInstance()._engine.rootContext())
self._print_context.setContextProperty("OutputDevice", self)
self._print_view = component.create(self._print_context)
if component.isError():
Logger.log("e", " Errors creating component: \n%s", "\n".join(
[e.toString() for e in component.errors()]))
if self._print_view is not None:
self._print_view.show()
## Store job info, show Print view for settings
def requestWrite(self, nodes, file_name=None, filter_by_machine=False, file_handler=None, **kwargs):
self._selected_printer = self._automatic_printer # reset to default option
self._request_job = [nodes, file_name, filter_by_machine, file_handler, kwargs]
if self._stage != OutputStage.ready:
if self._error_message:
self._error_message.hide()
self._error_message = Message(
i18n_catalog.i18nc("@info:status",
"Sending new jobs (temporarily) blocked, still sending the previous print job."))
self._error_message.show()
return
if len(self._printers) > 1:
self.spawnPrintView() # Ask user how to print it.
elif len(self._printers) == 1:
# If there is only one printer, don't bother asking.
self.selectAutomaticPrinter()
self.sendPrintJob()
else:
# Cluster has no printers, warn the user of this.
if self._error_message:
self._error_message.hide()
self._error_message = Message(
i18n_catalog.i18nc("@info:status",
"Unable to send new print job: this 3D printer is not (yet) set up to host a group of connected Ultimaker 3 printers."))
self._error_message.show()
## Actually send the print job, called from the dialog
# :param: require_printer_name: name of printer, or ""
@pyqtSlot()
def sendPrintJob(self):
nodes, file_name, filter_by_machine, file_handler, kwargs = self._request_job
require_printer_name = self._selected_printer["unique_name"]
self._send_gcode_start = time.time()
Logger.log("d", "Sending print job [%s] to host..." % file_name)
if self._stage != OutputStage.ready:
Logger.log("d", "Unable to send print job as the state is %s", self._stage)
raise OutputDeviceError.DeviceBusyError()
self._stage = OutputStage.uploading
self._file_name = "%s.gcode.gz" % file_name
self._showProgressMessage()
self._request = self._buildSendPrintJobHttpRequest(require_printer_name)
self._reply = self._manager.post(self._request, self._multipart)
self._reply.uploadProgress.connect(self._onUploadProgress)
# See _finishedPostPrintJobRequest()
def _buildSendPrintJobHttpRequest(self, require_printer_name):
api_url = QUrl(self._api_base_uri + "print_jobs/")
request = QNetworkRequest(api_url)
# Create multipart request and add the g-code.
self._multipart = QHttpMultiPart(QHttpMultiPart.FormDataType)
# Add gcode
part = QHttpPart()
part.setHeader(QNetworkRequest.ContentDispositionHeader,
'form-data; name="file"; filename="%s"' % self._file_name)
gcode = getattr(Application.getInstance().getController().getScene(), "gcode_list")
compressed_gcode = self._compressGcode(gcode)
if compressed_gcode is None:
return # User aborted print, so stop trying.
part.setBody(compressed_gcode)
self._multipart.append(part)
# require_printer_name "" means automatic
if require_printer_name:
self._multipart.append(self.__createKeyValueHttpPart("require_printer_name", require_printer_name))
user_name = self.__get_username()
if user_name is None:
user_name = "unknown"
self._multipart.append(self.__createKeyValueHttpPart("owner", user_name))
self._addUserAgentHeader(request)
return request
def _compressGcode(self, gcode):
self._compressing_print = True
batched_line = ""
max_chars_per_line = int(1024 * 1024 / 4) # 1 / 4 MB
byte_array_file_data = b""
def _compressDataAndNotifyQt(data_to_append):
compressed_data = gzip.compress(data_to_append.encode("utf-8"))
QCoreApplication.processEvents() # Ensure that the GUI does not freeze.
# Pretend that this is a response, as zipping might take a bit of time.
self._last_response_time = time.time()
return compressed_data
if gcode is None:
Logger.log("e", "Unable to find sliced gcode, returning empty.")
return byte_array_file_data
for line in gcode:
if not self._compressing_print:
self._progress_message.hide()
return # Stop trying to zip, abort was called.
batched_line += line
# if the gcode was read from a gcode file, self._gcode will be a list of all lines in that file.
# Compressing line by line in this case is extremely slow, so we need to batch them.
if len(batched_line) < max_chars_per_line:
continue
byte_array_file_data += _compressDataAndNotifyQt(batched_line)
batched_line = ""
# Also compress the leftovers.
if batched_line:
byte_array_file_data += _compressDataAndNotifyQt(batched_line)
return byte_array_file_data
def __createKeyValueHttpPart(self, key, value):
metadata_part = QHttpPart()
metadata_part.setHeader(QNetworkRequest.ContentTypeHeader, 'text/plain')
metadata_part.setHeader(QNetworkRequest.ContentDispositionHeader, 'form-data; name="%s"' % (key))
metadata_part.setBody(bytearray(value, "utf8"))
return metadata_part
def __get_username(self):
try:
return getpass.getuser()
except:
Logger.log("d", "Could not get the system user name, returning 'unknown' instead.")
return None
def _finishedPrintJobPostRequest(self, reply):
self._stage = OutputStage.ready
if self._progress_message:
self._progress_message.hide()
self._progress_message = None
self.writeFinished.emit(self)
if reply.error():
self._showRequestFailedMessage(reply)
self.writeError.emit(self)
else:
self._showRequestSucceededMessage()
self.writeSuccess.emit(self)
self._cleanupRequest()
def _showRequestFailedMessage(self, reply):
if reply is not None:
Logger.log("w", "Unable to send print job to group {cluster_name}: {error_string} ({error})".format(
cluster_name = self.getName(),
error_string = str(reply.errorString()),
error = str(reply.error())))
error_message_template = i18n_catalog.i18nc("@info:status", "Unable to send print job to group {cluster_name}.")
message = Message(text=error_message_template.format(
cluster_name = self.getName()))
message.show()
def _showRequestSucceededMessage(self):
confirmation_message_template = i18n_catalog.i18nc(
"@info:status",
"Sent {file_name} to group {cluster_name}."
)
file_name = os.path.basename(self._file_name).split(".")[0]
message_text = confirmation_message_template.format(cluster_name = self.getName(), file_name = file_name)
message = Message(text=message_text)
button_text = i18n_catalog.i18nc("@action:button", "Show print jobs")
button_tooltip = i18n_catalog.i18nc("@info:tooltip", "Opens the print jobs interface in your browser.")
message.addAction("open_browser", button_text, "globe", button_tooltip)
message.actionTriggered.connect(self._onMessageActionTriggered)
message.show()
def setPrintJobs(self, print_jobs):
#TODO: hack, last seen messes up the check, so drop it.
for job in print_jobs:
del job["last_seen"]
# Strip any extensions
job["name"] = self._removeGcodeExtension(job["name"])
if self._print_jobs != print_jobs:
old_print_jobs = self._print_jobs
self._print_jobs = print_jobs
self._notifyFinishedPrintJobs(old_print_jobs, print_jobs)
# Yes, this is a hacky way of doing it, but it's quick and the API doesn't give the print job per printer
# for some reason. ugh.
self._print_job_by_printer_uuid = {}
self._print_job_by_uuid = {}
for print_job in print_jobs:
if "printer_uuid" in print_job and print_job["printer_uuid"] is not None:
self._print_job_by_printer_uuid[print_job["printer_uuid"]] = print_job
self._print_job_by_uuid[print_job["uuid"]] = print_job
self.printJobsChanged.emit()
def _removeGcodeExtension(self, name):
parts = name.split(".")
if parts[-1].upper() == "GZ":
parts = parts[:-1]
if parts[-1].upper() == "GCODE":
parts = parts[:-1]
return ".".join(parts)
def _notifyFinishedPrintJobs(self, old_print_jobs, new_print_jobs):
"""Notify the user when any of their print jobs have just completed.
Arguments:
old_print_jobs -- the previous list of print job status information as returned by the cluster REST API.
new_print_jobs -- the current list of print job status information as returned by the cluster REST API.
"""
if old_print_jobs is None:
return
username = self.__get_username()
if username is None:
return
our_old_print_jobs = self.__filterOurPrintJobs(old_print_jobs)
our_old_not_finished_print_jobs = [pj for pj in our_old_print_jobs if pj["status"] != "wait_cleanup"]
our_new_print_jobs = self.__filterOurPrintJobs(new_print_jobs)
our_new_finished_print_jobs = [pj for pj in our_new_print_jobs if pj["status"] == "wait_cleanup"]
old_not_finished_print_job_uuids = set([pj["uuid"] for pj in our_old_not_finished_print_jobs])
for print_job in our_new_finished_print_jobs:
if print_job["uuid"] in old_not_finished_print_job_uuids:
printer_name = self.__getPrinterNameFromUuid(print_job["printer_uuid"])
if printer_name is None:
printer_name = i18n_catalog.i18nc("@info:status", "Unknown printer")
message_text = (i18n_catalog.i18nc("@info:status",
"Printer '{printer_name}' has finished printing '{job_name}'.")
.format(printer_name=printer_name, job_name=print_job["name"]))
message = Message(text=message_text, title=i18n_catalog.i18nc("@info:status", "Print finished"))
Application.getInstance().showMessage(message)
Application.getInstance().showToastMessage(
i18n_catalog.i18nc("@info:status", "Print finished"),
message_text)
def __filterOurPrintJobs(self, print_jobs):
username = self.__get_username()
return [print_job for print_job in print_jobs if print_job["owner"] == username]
def __getPrinterNameFromUuid(self, printer_uuid):
for printer in self._printers:
if printer["uuid"] == printer_uuid:
return printer["friendly_name"]
return None
def setPrinters(self, printers):
if self._printers != printers:
self._connected_printers_type_count = []
printers_count = {}
self._printers = printers
self._printers_dict = dict((p["unique_name"], p) for p in printers) # for easy lookup by unique_name
for printer in printers:
variant = printer["machine_variant"]
if variant in printers_count:
printers_count[variant] += 1
else:
printers_count[variant] = 1
for type in printers_count:
self._connected_printers_type_count.append({"machine_type": type, "count": printers_count[type]})
self.printersChanged.emit()
@pyqtProperty("QVariantList", notify=printersChanged)
def connectedPrintersTypeCount(self):
return self._connected_printers_type_count
@pyqtProperty("QVariantList", notify=printersChanged)
def connectedPrinters(self):
return self._printers
@pyqtProperty(int, notify=printJobsChanged)
def numJobsPrinting(self):
num_jobs_printing = 0
for job in self._print_jobs:
if job["status"] == "printing":
num_jobs_printing += 1
return num_jobs_printing
@pyqtProperty(int, notify=printJobsChanged)
def numJobsQueued(self):
num_jobs_queued = 0
for job in self._print_jobs:
if job["status"] == "queued":
num_jobs_queued += 1
return num_jobs_queued
@pyqtProperty("QVariantMap", notify=printJobsChanged)
def printJobsByUUID(self):
return self._print_job_by_uuid
@pyqtProperty("QVariantMap", notify=printJobsChanged)
def printJobsByPrinterUUID(self):
return self._print_job_by_printer_uuid
@pyqtProperty("QVariantList", notify=printJobsChanged)
def printJobs(self):
return self._print_jobs
@pyqtProperty("QVariantList", notify=printersChanged)
def printers(self):
return [self._automatic_printer, ] + self._printers
@pyqtSlot(str, str)
def selectPrinter(self, unique_name, friendly_name):
self.stopCamera()
self._selected_printer = {"unique_name": unique_name, "friendly_name": friendly_name}
Logger.log("d", "Selected printer: %s %s", friendly_name, unique_name)
# TODO: Probably not the nicest way to do this. This needs to be done better at some point in time.
if unique_name == "":
self._address = self._master_address
else:
self._address = self._printers_dict[self._selected_printer["unique_name"]]["ip_address"]
self.selectedPrinterChanged.emit()
def _updateJobState(self, job_state):
name = self._selected_printer.get("friendly_name")
if name == "" or name == "Automatic":
# TODO: This is now a bit hacked; If no printer is selected, don't show job state.
if self._job_state != "":
self._job_state = ""
self.jobStateChanged.emit()
else:
if self._job_state != job_state:
self._job_state = job_state
self.jobStateChanged.emit()
@pyqtSlot()
def selectAutomaticPrinter(self):
self.stopCamera()
self._selected_printer = self._automatic_printer
self.selectedPrinterChanged.emit()
@pyqtProperty("QVariant", notify=selectedPrinterChanged)
def selectedPrinterName(self):
return self._selected_printer.get("unique_name", "")
def getPrintJobsUrl(self):
return self._host + "/print_jobs"
def getPrintersUrl(self):
return self._host + "/printers"
def _showProgressMessage(self):
progress_message_template = i18n_catalog.i18nc("@info:progress",
"Sending <filename>{file_name}</filename> to group {cluster_name}")
file_name = os.path.basename(self._file_name).split(".")[0]
self._progress_message = Message(progress_message_template.format(file_name = file_name, cluster_name = self.getName()), 0, False, -1)
self._progress_message.addAction("Abort", i18n_catalog.i18nc("@action:button", "Cancel"), None, "")
self._progress_message.actionTriggered.connect(self._onMessageActionTriggered)
self._progress_message.show()
def _addUserAgentHeader(self, request):
request.setRawHeader(b"User-agent", b"CuraPrintClusterOutputDevice Plugin")
def _cleanupRequest(self):
self._reply = None
self._request = None
self._multipart = None
self._stage = OutputStage.ready
self._file_name = None
def _onFinished(self, reply):
super()._onFinished(reply)
reply_url = reply.url().toString()
status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
if status_code == 500:
Logger.log("w", "Request to {url} returned a 500.".format(url = reply_url))
return
if reply.error() == QNetworkReply.ContentOperationNotPermittedError:
# It was probably "/api/v1/materials" for legacy UM3
return
if reply.error() == QNetworkReply.ContentNotFoundError:
# It was probably "/api/v1/print_job" for legacy UM3
return
if reply.operation() == QNetworkAccessManager.PostOperation:
if self._cluster_api_prefix + "print_jobs" in reply_url:
self._finishedPrintJobPostRequest(reply)
return
# We need to do this check *after* we process the post operation!
# If the sending of g-code is cancelled by the user it will result in an error, but we do need to handle this.
if reply.error() != QNetworkReply.NoError:
Logger.log("e", "After requesting [%s] we got a network error [%s]. Not processing anything...", reply_url, reply.error())
return
elif reply.operation() == QNetworkAccessManager.GetOperation:
if self._cluster_api_prefix + "print_jobs" in reply_url:
self._finishedPrintJobsRequest(reply)
elif self._cluster_api_prefix + "printers" in reply_url:
self._finishedPrintersRequest(reply)
@pyqtSlot()
def openPrintJobControlPanel(self):
Logger.log("d", "Opening print job control panel...")
QDesktopServices.openUrl(QUrl(self.getPrintJobsUrl()))
@pyqtSlot()
def openPrinterControlPanel(self):
Logger.log("d", "Opening printer control panel...")
QDesktopServices.openUrl(QUrl(self.getPrintersUrl()))
def _onMessageActionTriggered(self, message, action):
if action == "open_browser":
QDesktopServices.openUrl(QUrl(self.getPrintJobsUrl()))
if action == "Abort":
Logger.log("d", "User aborted sending print to remote.")
self._progress_message.hide()
self._compressing_print = False
self._stage = OutputStage.ready
if self._reply:
self._reply.abort()
self._reply = None
Application.getInstance().showPrintMonitor.emit(False)

View File

@ -17,7 +17,7 @@ import cura.Settings.ExtruderManager
from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager, QNetworkReply
from PyQt5.QtCore import QUrl, QTimer, pyqtSignal, pyqtProperty, pyqtSlot, QCoreApplication
from PyQt5.QtGui import QImage
from PyQt5.QtGui import QImage, QColor
from PyQt5.QtWidgets import QMessageBox
import json
@ -102,7 +102,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
self._target_bed_temperature = 0
self._processing_preheat_requests = True
self.setPriority(2) # 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(key)
self.setShortDescription(i18n_catalog.i18nc("@action:button Preceded by 'Ready to'.", "Print over network"))
self.setDescription(i18n_catalog.i18nc("@properties:tooltip", "Print over network"))
@ -1007,7 +1007,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
reply_url = reply.url().toString()
if reply.operation() == QNetworkAccessManager.GetOperation:
if "printer" in reply_url: # Status update from printer.
# "printer" is also in "printers", therefore _api_prefix is added.
if self._api_prefix + "printer" in reply_url: # Status update from printer.
if status_code == 200:
if self._connection_state == ConnectionState.connecting:
self.setConnectionState(ConnectionState.connected)
@ -1025,7 +1026,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
else:
Logger.log("w", "We got an unexpected status (%s) while requesting printer state", status_code)
pass # TODO: Handle errors
elif "print_job" in reply_url: # Status update from print_job:
elif self._api_prefix + "print_job" in reply_url: # Status update from print_job:
if status_code == 200:
try:
json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))

View File

@ -1,26 +1,31 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
from . import NetworkPrinterOutputDevice
from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange, ServiceInfo # type: ignore
from UM.Logger import Logger
from UM.Signal import Signal, signalemitter
from UM.Application import Application
from UM.Preferences import Preferences
from PyQt5.QtNetwork import QNetworkRequest, QNetworkAccessManager, QNetworkReply
from PyQt5.QtCore import QUrl
import os
import time
import json
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtNetwork import QNetworkRequest, QNetworkAccessManager, QNetworkReply
from PyQt5.QtQml import QQmlComponent, QQmlContext
from UM.Application import Application
from UM.Logger import Logger
from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
from UM.PluginRegistry import PluginRegistry
from UM.Preferences import Preferences
from UM.Signal import Signal, signalemitter
from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange, ServiceInfo # type: ignore
from . import NetworkPrinterOutputDevice, NetworkClusterPrinterOutputDevice
## This plugin handles the connection detection & creation of output device objects for the UM3 printer.
# Zero-Conf is used to detect printers, which are saved in a dict.
# If we discover a printer that has the same key as the active machine instance a connection is made.
@signalemitter
class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin):
class NetworkPrinterOutputDevicePlugin(QObject, OutputDevicePlugin):
def __init__(self):
super().__init__()
self._zero_conf = None
@ -29,6 +34,8 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin):
self._api_version = "1"
self._api_prefix = "/api/v" + self._api_version + "/"
self._cluster_api_version = "1"
self._cluster_api_prefix = "/cluster-api/v" + self._cluster_api_version + "/"
self._network_manager = QNetworkAccessManager()
self._network_manager.finished.connect(self._onNetworkRequestFinished)
@ -47,6 +54,8 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin):
self._preferences.addPreference("um3networkprinting/manual_instances", "") # A comma-separated list of ip adresses or hostnames
self._manual_instances = self._preferences.getValue("um3networkprinting/manual_instances").split(",")
self._network_requests_buffer = {} # store api responses until data is complete
addPrinterSignal = Signal()
removePrinterSignal = Signal()
printerListChanged = Signal()
@ -91,6 +100,7 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin):
self.addPrinter(instance_name, address, properties)
self.checkManualPrinter(address)
self.checkClusterPrinter(address)
def removeManualPrinter(self, key, address = None):
if key in self._printers:
@ -105,18 +115,26 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin):
def checkManualPrinter(self, address):
# Check if a printer exists at this address
# If a printer responds, it will replace the preliminary printer created above
url = QUrl("http://" + address + self._api_prefix + "system")
# origin=manual is for tracking back the origin of the call
url = QUrl("http://" + address + self._api_prefix + "system?origin=manual_name")
name_request = QNetworkRequest(url)
self._network_manager.get(name_request)
def checkClusterPrinter(self, address):
cluster_url = QUrl("http://" + address + self._cluster_api_prefix + "printers/?origin=check_cluster")
cluster_request = QNetworkRequest(cluster_url)
self._network_manager.get(cluster_request)
## Handler for all requests that have finished.
def _onNetworkRequestFinished(self, reply):
reply_url = reply.url().toString()
status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
if reply.operation() == QNetworkAccessManager.GetOperation:
if "system" in reply_url: # Name returned from printer.
address = reply.url().host()
if "origin=manual_name" in reply_url: # Name returned from printer.
if status_code == 200:
try:
system_info = json.loads(bytes(reply.readAll()).decode("utf-8"))
except json.JSONDecodeError:
@ -125,28 +143,51 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin):
except UnicodeDecodeError:
Logger.log("e", "Printer returned incorrect UTF-8.")
return
address = reply.url().host()
instance_name = "manual:%s" % address
machine = "unknown"
if "variant" in system_info:
variant = system_info["variant"]
if variant == "Ultimaker 3":
machine = "9066"
elif variant == "Ultimaker 3 Extended":
machine = "9511"
properties = {
b"name": system_info["name"].encode("utf-8"),
b"address": address.encode("utf-8"),
b"firmware_version": system_info["firmware"].encode("utf-8"),
b"manual": b"true",
b"machine": machine.encode("utf-8")
}
if instance_name in self._printers:
# Only replace the printer if it is still in the list of (manual) printers
self.removePrinter(instance_name)
self.addPrinter(instance_name, address, properties)
if address not in self._network_requests_buffer:
self._network_requests_buffer[address] = {}
self._network_requests_buffer[address]["system"] = system_info
elif "origin=check_cluster" in reply_url:
if address not in self._network_requests_buffer:
self._network_requests_buffer[address] = {}
if status_code == 200:
# We know it's a cluster printer
Logger.log("d", "Cluster printer detected: [%s]", reply.url())
self._network_requests_buffer[address]["cluster"] = True
else:
Logger.log("d", "This url is not from a cluster printer: [%s]", reply.url())
self._network_requests_buffer[address]["cluster"] = False
# Both the system call and cluster call are finished
if (address in self._network_requests_buffer and
"system" in self._network_requests_buffer[address] and
"cluster" in self._network_requests_buffer[address]):
instance_name = "manual:%s" % address
system_info = self._network_requests_buffer[address]["system"]
is_cluster = self._network_requests_buffer[address]["cluster"]
machine = "unknown"
if "variant" in system_info:
variant = system_info["variant"]
if variant == "Ultimaker 3":
machine = "9066"
elif variant == "Ultimaker 3 Extended":
machine = "9511"
properties = {
b"name": system_info["name"].encode("utf-8"),
b"address": address.encode("utf-8"),
b"firmware_version": system_info["firmware"].encode("utf-8"),
b"manual": b"true",
b"machine": machine.encode("utf-8")
}
if instance_name in self._printers:
# Only replace the printer if it is still in the list of (manual) printers
self.removePrinter(instance_name)
self.addPrinter(instance_name, address, properties, force_cluster=is_cluster)
del self._network_requests_buffer[address]
## Stop looking for devices on network.
def stop(self):
@ -175,8 +216,13 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin):
self._printers[key].connectionStateChanged.disconnect(self._onPrinterConnectionStateChanged)
## Because the model needs to be created in the same thread as the QMLEngine, we use a signal.
def addPrinter(self, name, address, properties):
printer = NetworkPrinterOutputDevice.NetworkPrinterOutputDevice(name, address, properties, self._api_prefix)
def addPrinter(self, name, address, properties, force_cluster=False):
cluster_size = int(properties.get(b"cluster_size", -1))
if force_cluster or cluster_size >= 0:
printer = NetworkClusterPrinterOutputDevice.NetworkClusterPrinterOutputDevice(
name, address, properties, self._api_prefix, self._plugin_path)
else:
printer = NetworkPrinterOutputDevice.NetworkPrinterOutputDevice(name, address, properties, self._api_prefix)
self._printers[printer.getKey()] = printer
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack and printer.getKey() == global_container_stack.getMetaDataEntry("um_network_key"):
@ -238,3 +284,21 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin):
elif state_change == ServiceStateChange.Removed:
Logger.log("d", "Bonjour service removed: %s" % name)
self.removePrinterSignal.emit(str(name))
## For cluster below
def _get_plugin_directory_name(self):
current_file_absolute_path = os.path.realpath(__file__)
directory_path = os.path.dirname(current_file_absolute_path)
_, directory_name = os.path.split(directory_path)
return directory_name
@property
def _plugin_path(self):
return PluginRegistry.getInstance().getPluginPath(self._get_plugin_directory_name())
@pyqtSlot()
def openControlPanel(self):
Logger.log("d", "Opening print jobs web UI...")
selected_device = self.getOutputDeviceManager().getActiveDevice()
if isinstance(selected_device, NetworkClusterPrinterOutputDevice.NetworkClusterPrinterOutputDevice):
QDesktopServices.openUrl(QUrl(selected_device.getPrintJobsUrl()))

View File

@ -0,0 +1,18 @@
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.1
import UM 1.1 as UM
Button {
objectName: "openPanelSaveAreaButton"
id: openPanelSaveAreaButton
UM.I18nCatalog { id: catalog; name: "cura"; }
height: UM.Theme.getSize("save_button_save_to_button").height
tooltip: catalog.i18nc("@info:tooltip", "Opens the print jobs page with your default web browser.")
text: catalog.i18nc("@action:button", "View print jobs")
style: UM.Theme.styles.sidebar_action_button
}

View File

@ -0,0 +1,33 @@
import QtQuick 2.2
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.2 as UM
Item
{
id: extruderInfo
property var printCoreConfiguration
width: parent.width / 2
height: childrenRect.height
Label
{
id: materialLabel
text: printCoreConfiguration.material.material + " (" + printCoreConfiguration.material.color + ")"
elide: Text.ElideRight
width: parent.width
font: UM.Theme.getFont("very_small")
}
Label
{
id: printCoreLabel
text: printCoreConfiguration.print_core_id
anchors.top: materialLabel.bottom
elide: Text.ElideRight
width: parent.width
font: UM.Theme.getFont("very_small")
opacity: 0.5
}
}

View File

@ -0,0 +1,103 @@
// Copyright (c) 2015 Ultimaker B.V.
// Cura is released under the terms of the AGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Window 2.2
import QtQuick.Controls 1.2
import UM 1.1 as UM
UM.Dialog
{
id: base;
minimumWidth: 500
minimumHeight: 140
maximumWidth: minimumWidth
maximumHeight: minimumHeight
width: minimumWidth
height: minimumHeight
visible: true
modality: Qt.ApplicationModal
title: catalog.i18nc("@title:window","Print over network")
Column
{
id: printerSelection
anchors.fill: parent
anchors.top: parent.top
anchors.topMargin: UM.Theme.getSize("default_margin").height
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.rightMargin: UM.Theme.getSize("default_margin").width
height: 50
Label
{
id: manualPrinterSelectionLabel
anchors
{
left: parent.left
topMargin: UM.Theme.getSize("default_margin").height
right: parent.right
}
text: "Printer selection"
wrapMode: Text.Wrap
height: 20
}
ComboBox
{
id: printerSelectionCombobox
model: OutputDevice.printers
textRole: "friendly_name"
width: parent.width
height: 40
Behavior on height { NumberAnimation { duration: 100 } }
onActivated:
{
var printerData = OutputDevice.printers[index];
OutputDevice.selectPrinter(printerData.unique_name, printerData.friendly_name);
}
}
SystemPalette
{
id: palette
}
UM.I18nCatalog { id: catalog; name: "cura"; }
}
leftButtons: [
Button
{
text: catalog.i18nc("@action:button","Cancel")
enabled: true
onClicked: {
base.visible = false;
// reset to defaults
OutputDevice.selectAutomaticPrinter()
printerSelectionCombobox.currentIndex = 0
}
}
]
rightButtons: [
Button
{
text: catalog.i18nc("@action:button","Print")
enabled: true
onClicked: {
base.visible = false;
OutputDevice.sendPrintJob();
// reset to defaults
OutputDevice.selectAutomaticPrinter()
printerSelectionCombobox.currentIndex = 0
}
}
]
}

View File

@ -0,0 +1,345 @@
import QtQuick 2.2
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.3 as UM
Rectangle
{
function strPadLeft(string, pad, length)
{
return (new Array(length + 1).join(pad) + string).slice(-length);
}
function getPrettyTime(time)
{
var hours = Math.floor(time / 3600)
time -= hours * 3600
var minutes = Math.floor(time / 60);
time -= minutes * 60
var seconds = Math.floor(time);
var finalTime = strPadLeft(hours, "0", 2) + ':' + strPadLeft(minutes,'0',2)+ ':' + strPadLeft(seconds,'0',2);
return finalTime;
}
function formatPrintJobPercent(printJob)
{
if (printJob == null)
{
return "";
}
if (printJob.time_total === 0)
{
return "";
}
return Math.min(100, Math.round(printJob.time_elapsed / printJob.time_total * 100)) + "%";
}
id: printerDelegate
property var printer
border.width: UM.Theme.getSize("default_lining").width
border.color: mouse.containsMouse ? UM.Theme.getColor("setting_control_border_highlight") : lineColor
z: mouse.containsMouse ? 1 : 0 // Push this item up a bit on mouse over to ensure that the highlighted bottom border is visible.
property var printJob:
{
if (printer.reserved_by != null)
{
// Look in another list.
return OutputDevice.printJobsByUUID[printer.reserved_by]
}
return OutputDevice.printJobsByPrinterUUID[printer.uuid]
}
MouseArea
{
id: mouse
anchors.fill:parent
onClicked: OutputDevice.selectPrinter(printer.unique_name, printer.friendly_name)
hoverEnabled: true;
// Only clickable if no printer is selected
enabled: OutputDevice.selectedPrinterName == ""
}
Row
{
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.margins: UM.Theme.getSize("default_margin").width
Rectangle
{
width: parent.width / 3
height: parent.height
Label // Print job name
{
id: jobNameLabel
anchors.top: parent.top
anchors.left: parent.left
text: printJob != null ? printJob.name : ""
font: UM.Theme.getFont("default_bold")
}
Label
{
id: jobOwnerLabel
anchors.top: jobNameLabel.bottom
text: printJob != null ? printJob.owner : ""
opacity: 0.50
}
Label
{
id: totalTimeLabel
anchors.bottom: parent.bottom
text: printJob != null ? getPrettyTime(printJob.time_total) : ""
opacity: 0.65
font: UM.Theme.getFont("default")
}
}
Rectangle
{
width: parent.width / 3 * 2
height: parent.height
Label // Friendly machine name
{
id: printerNameLabel
anchors.top: parent.top
anchors.left: parent.left
width: parent.width / 2 - UM.Theme.getSize("default_margin").width - showCameraIcon.width
text: printer.friendly_name
font: UM.Theme.getFont("default_bold")
elide: Text.ElideRight
}
Label // Machine variant
{
id: printerTypeLabel
anchors.top: printerNameLabel.bottom
width: parent.width / 2 - UM.Theme.getSize("default_margin").width
text: printer.machine_variant
anchors.left: parent.left
elide: Text.ElideRight
font: UM.Theme.getFont("very_small")
opacity: 0.50
}
Rectangle // Camera icon
{
id: showCameraIcon
width: 40
height: width
radius: width
anchors.right: printProgressArea.left
anchors.rightMargin: UM.Theme.getSize("default_margin").width
color: emphasisColor
UM.RecolorImage
{
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
source: "camera-icon.svg"
width: sourceSize.width
height: sourceSize.height * width / sourceSize.width
color: "white"
}
}
Row // PrintCode config
{
id: extruderInfo
anchors.bottom: parent.bottom
width: parent.width / 2 - UM.Theme.getSize("default_margin").width
height: childrenRect.height
spacing: 10
PrintCoreConfiguration
{
id: leftExtruderInfo
width: (parent.width-1) / 2
printCoreConfiguration: printer.configuration[0]
}
Rectangle
{
id: extruderSeperator
width: 1
height: parent.height
color: lineColor
}
PrintCoreConfiguration
{
id: rightExtruderInfo
width: (parent.width-1) / 2
printCoreConfiguration: printer.configuration[1]
}
}
Rectangle // Print progress
{
id: printProgressArea
anchors.right: parent.right
anchors.top: parent.top
height: showExtended ? parent.height: printProgressTitleBar.height
width: parent.width / 2 - UM.Theme.getSize("default_margin").width
border.width: UM.Theme.getSize("default_lining").width
border.color: lineColor
radius: cornerRadius
property var showExtended: {
if(printJob!= null)
{
var extendStates = ["sent_to_printer", "wait_for_configuration", "printing", "pre_print", "post_print", "wait_cleanup"];
return extendStates.indexOf(printJob.status) !== -1;
}
return false
}
visible:
{
return true
}
Item // Status and Percent
{
id: printProgressTitleBar
width: parent.width
//border.width: UM.Theme.getSize("default_lining").width
//border.color: lineColor
height: 40
anchors.left: parent.left
Label
{
id: statusText
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.verticalCenter: parent.verticalCenter
anchors.right: progressText.left
anchors.rightMargin: UM.Theme.getSize("default_margin").width
text: {
if(printJob != null)
{
if(printJob.status == "printing" || printJob.status == "post_print")
{
return catalog.i18nc("@label:status", "Printing")
}
else if(printJob.status == "wait_for_configuration")
{
return catalog.i18nc("@label:status", "Reserved")
}
else if(printJob.status == "wait_cleanup")
{
return catalog.i18nc("@label:status", "Finished")
}
else if (printJob.status == "pre_print" || printJob.status == "sent_to_printer")
{
return catalog.i18nc("@label:status", "Preparing")
}
else
{
return ""
}
}
return catalog.i18nc("@label:status", "Available")
}
elide: Text.ElideRight
font: UM.Theme.getFont("small")
}
Label
{
id: progressText
anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
anchors.top: statusText.top
text: formatPrintJobPercent(printJob)
visible: printJob != null && (["printing", "post_print", "pre_print", "sent_to_printer"].indexOf(printJob.status) !== -1)
opacity: 0.65
font: UM.Theme.getFont("very_small")
}
Rectangle
{
//TODO: This will become a progress bar in the future
width: parent.width
height: UM.Theme.getSize("default_lining").height
anchors.bottom: parent.bottom
anchors.left: parent.left
visible: printProgressArea.showExtended
color: lineColor
}
}
Column
{
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.top: printProgressTitleBar.bottom
anchors.topMargin: UM.Theme.getSize("default_margin").height
width: parent.width - 2 * UM.Theme.getSize("default_margin").width
visible: printJob != null && (["wait_cleanup", "printing", "pre_print", "wait_for_configuration"].indexOf(printJob.status) !== -1)
Label // Status detail
{
text:
{
if(printJob != null)
{
if(printJob.status == "printing" )
{
return catalog.i18nc("@label", "Finishes at: ") + OutputDevice.getTimeCompleted(printJob.time_total - printJob.time_elapsed)
}
if(printJob.status == "wait_cleanup")
{
return catalog.i18nc("@label", "Clear build plate")
}
if(printJob.status == "sent_to_printer" || printJob.status == "pre_print")
{
return catalog.i18nc("@label", "Preparing to print")
}
if(printJob.status == "wait_for_configuration")
{
return catalog.i18nc("@label", "Not accepting print jobs")
}
}
return ""
}
elide: Text.ElideRight
font: UM.Theme.getFont("default")
}
Label // Status 2nd row
{
text: {
if(printJob != null) {
if(printJob.status == "printing" )
{
return OutputDevice.getDateCompleted(printJob.time_total - printJob.time_elapsed)
}
}
return "";
}
elide: Text.ElideRight
font: UM.Theme.getFont("default")
}
}
}
}
}
}

View File

@ -0,0 +1,54 @@
import QtQuick 2.2
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.3 as UM
import Cura 1.0 as Cura
Rectangle
{
id: base
width: 250
height: 250
signal clicked()
MouseArea
{
anchors.fill:parent
onClicked: base.clicked()
}
Rectangle
{
// TODO: Actually add UM icon / picture
width: 100
height: 100
border.width: UM.Theme.getSize("default_lining").width
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: UM.Theme.getSize("default_margin").height
}
Label
{
id: nameLabel
anchors.bottom: ipLabel.top
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.rightMargin: UM.Theme.getSize("default_margin").width
text: modelData.friendly_name.toString()
font: UM.Theme.getFont("large")
elide: Text.ElideMiddle;
height: UM.Theme.getSize("section").height;
}
Label
{
id: ipLabel
text: modelData.ip_address.toString()
anchors.bottom: parent.bottom
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
font: UM.Theme.getFont("default")
height:10
anchors.horizontalCenter: parent.horizontalCenter
}
}

View File

@ -0,0 +1,91 @@
import QtQuick 2.2
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.3 as UM
Item
{
Rectangle
{
anchors.fill:parent
color: UM.Theme.getColor("viewport_overlay")
opacity: 0.5
}
MouseArea
{
anchors.fill: parent
onClicked: OutputDevice.selectAutomaticPrinter()
z: 0
}
Button
{
id: backButton
anchors.bottom: cameraImage.top
anchors.bottomMargin: UM.Theme.getSize("default_margin").width
anchors.right: cameraImage.right
// TODO: Harcoded sizes
width: 20
height: 20
onClicked: OutputDevice.selectAutomaticPrinter()
style: ButtonStyle
{
label: Item
{
UM.RecolorImage
{
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
width: control.width
height: control.height
sourceSize.width: width
sourceSize.height: width
source: UM.Theme.getIcon("cross1")
}
}
background: Item {}
}
}
Image
{
id: cameraImage
width: Math.min(sourceSize.width === 0 ? 800 : sourceSize.width, maximumWidth)
height: (sourceSize.height === 0 ? 600 : sourceSize.height) * width / sourceSize.width
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
z: 1
onVisibleChanged:
{
if(visible)
{
OutputDevice.startCamera()
} else
{
OutputDevice.stopCamera()
}
}
source:
{
if(OutputDevice.cameraImage)
{
return OutputDevice.cameraImage;
}
return "";
}
}
MouseArea
{
anchors.fill: cameraImage
onClicked: { /* no-op */ }
z: 1
}
}

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="21" height="14" viewBox="0 0 21 14">
<path fill="none" fill-rule="evenodd" stroke="#464B4E" stroke-width="1.5" d="M19.295 2.83L16.25 4.31V2c0-.69-.56-1.25-1.25-1.25H2C1.31.75.75 1.31.75 2v10c0 .69.56 1.25 1.25 1.25h13c.69 0 1.25-.56 1.25-1.25V9.69l3.045 1.48a.85.85 0 0 0 .367.08c.355 0 .584-.181.584-.31V3.06c0-.026-.011-.058-.04-.096-.16-.206-.592-.289-.911-.134z" opacity=".85"/>
</svg>

After

Width:  |  Height:  |  Size: 441 B

View File

@ -11,10 +11,10 @@ UM.Dialog
{
id: base;
width: 500 * Screen.devicePixelRatio;
minimumWidth: 500 * Screen.devicePixelRatio;
height: 100 * Screen.devicePixelRatio;
minimumHeight: 100 * Screen.devicePixelRatio;
width: minimumWidth;
minimumWidth: 500 * screenScaleFactor;
height: minimumHeight;
minimumHeight: 100 * screenScaleFactor;
visible: true;
modality: Qt.ApplicationModal;

View File

@ -4094,7 +4094,7 @@
"raft_smoothing":
{
"label": "Raft Smoothing",
"description": "This setting control how much inner corners in the raft outline are rounded. Inward corners are rounded to a semi circle with a radius equal to the value given here. This setting also removes holes in the raft outline which are smaller than such a circle.",
"description": "This setting controls how much inner corners in the raft outline are rounded. Inward corners are rounded to a semi circle with a radius equal to the value given here. This setting also removes holes in the raft outline which are smaller than such a circle.",
"unit": "mm",
"type": "float",
"default_value": 5,

View File

@ -14,8 +14,8 @@ UM.Dialog
//: About dialog title
title: catalog.i18nc("@title:window","About Cura")
minimumWidth: 500
minimumHeight: 650
minimumWidth: 500 * screenScaleFactor
minimumHeight: 650 * screenScaleFactor
width: minimumWidth
height: minimumHeight

View File

@ -18,8 +18,8 @@ UM.Dialog
id: base
title: catalog.i18nc("@title:window", "Open project file")
width: 450
height: 150
width: 450 * screenScaleFactor
height: 150 * screenScaleFactor
maximumHeight: height
maximumWidth: width
@ -61,10 +61,10 @@ UM.Dialog
Column
{
anchors.fill: parent
anchors.leftMargin: 20
anchors.rightMargin: 20
anchors.bottomMargin: 20
spacing: 10
anchors.leftMargin: 20 * screenScaleFactor
anchors.rightMargin: 20 * screenScaleFactor
anchors.bottomMargin: 20 * screenScaleFactor
spacing: 10 * screenScaleFactor
Label
{

View File

@ -14,8 +14,8 @@ UM.Dialog
id: base
title: catalog.i18nc("@title:window", "Discard or Keep changes")
width: 800
height: 400
width: 800 * screenScaleFactor
height: 400 * screenScaleFactor
property var changesModel: Cura.UserChangesModel{ id: userChangesModel}
onVisibilityChanged:
{

View File

@ -67,7 +67,7 @@ Button
height: UM.Theme.getSize("extruder_button_material").height
radius: width / 2
border.width: 1
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("extruder_button_material_border")
opacity: !base.enabled ? 0.2 : 1.0

View File

@ -17,8 +17,8 @@ UM.Dialog
id: base
title: catalog.i18nc("@title:window", "Open file(s)")
width: 420
height: 170
width: 420 * screenScaleFactor
height: 170 * screenScaleFactor
maximumHeight: height
maximumWidth: width
@ -28,7 +28,7 @@ UM.Dialog
modality: UM.Application.platform == "linux" ? Qt.NonModal : Qt.WindowModal;
property var fileUrls: []
property int spacerHeight: 10
property int spacerHeight: 10 * screenScaleFactor
function loadProjectFile(projectFile)
{
@ -52,12 +52,12 @@ UM.Dialog
Column
{
anchors.fill: parent
anchors.leftMargin: 20
anchors.rightMargin: 20
anchors.bottomMargin: 20
anchors.leftMargin: 20 * screenScaleFactor
anchors.rightMargin: 20 * screenScaleFactor
anchors.bottomMargin: 20 * screenScaleFactor
anchors.left: parent.left
anchors.right: parent.right
spacing: 10
spacing: 10 * screenScaleFactor
Label
{

View File

@ -513,7 +513,7 @@ UM.PreferencesPage
Column
{
spacing: 4
spacing: 4 * screenScaleFactor
Label
{
@ -523,7 +523,7 @@ UM.PreferencesPage
ComboBox
{
id: choiceOnOpenProjectDropDownButton
width: 200
width: 200 * screenScaleFactor
model: ListModel
{
@ -572,7 +572,7 @@ UM.PreferencesPage
Column
{
spacing: 4
spacing: 4 * screenScaleFactor
Label
{
@ -583,7 +583,7 @@ UM.PreferencesPage
ComboBox
{
id: choiceOnProfileOverrideDropDownButton
width: 200
width: 200 * screenScaleFactor
model: ListModel
{

View File

@ -91,7 +91,7 @@ UM.ManagementPage
Item
{
width: childrenRect.width + 2
width: childrenRect.width + 2 * screenScaleFactor
height: childrenRect.height
Button
{
@ -112,8 +112,6 @@ UM.ManagementPage
{
id: actionDialog
property var content
minimumWidth: 350
minimumHeight: 350
onContentChanged:
{
contents = content;
@ -257,8 +255,8 @@ UM.ManagementPage
UM.RenameDialog
{
id: renameDialog;
width: 300
height: 150
width: 300 * screenScaleFactor
height: 150 * screenScaleFactor
object: base.currentItem && base.currentItem.name ? base.currentItem.name : "";
property var machine_name_validator: Cura.MachineNameValidator { }
validName: renameDialog.newName.match(renameDialog.machine_name_validator.machineNameRegex) != null;

View File

@ -119,8 +119,8 @@ SettingItem
UM.RecolorImage {
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width/2.5
height: parent.height/2.5
width: parent.width / 2.5
height: parent.height / 2.5
sourceSize.width: width
sourceSize.height: width
color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text");

View File

@ -86,8 +86,8 @@ SettingItem
source: UM.Theme.getIcon("arrow_bottom")
width: UM.Theme.getSize("standard_arrow").width
height: UM.Theme.getSize("standard_arrow").height
sourceSize.width: width + 5
sourceSize.height: width + 5
sourceSize.width: width + 5 * screenScaleFactor
sourceSize.height: width + 5 * screenScaleFactor
color: UM.Theme.getColor("setting_control_text");

View File

@ -137,8 +137,8 @@ SettingItem
source: UM.Theme.getIcon("arrow_bottom")
width: UM.Theme.getSize("standard_arrow").width
height: UM.Theme.getSize("standard_arrow").height
sourceSize.width: width + 5
sourceSize.height: width + 5
sourceSize.width: width + 5 * screenScaleFactor
sourceSize.height: width + 5 * screenScaleFactor
color: UM.Theme.getColor("setting_control_text")
}

View File

@ -156,8 +156,8 @@ SettingItem
source: UM.Theme.getIcon("arrow_bottom")
width: UM.Theme.getSize("standard_arrow").width
height: UM.Theme.getSize("standard_arrow").height
sourceSize.width: width + 5
sourceSize.height: width + 5
sourceSize.width: width + 5 * screenScaleFactor
sourceSize.height: width + 5 * screenScaleFactor
color: UM.Theme.getColor("setting_control_text")
}

View File

@ -213,7 +213,7 @@ Item
{
id: groovechildrect
width: base.width * 0.55
height: 2
height: 2 * screenScaleFactor
color: UM.Theme.getColor("quality_slider_unavailable")
anchors.verticalCenter: qualitySlider.verticalCenter
x: 0
@ -229,8 +229,8 @@ Item
{
anchors.verticalCenter: parent.verticalCenter
color: Cura.ProfilesModel.getItem(index).available ? UM.Theme.getColor("quality_slider_available") : UM.Theme.getColor("quality_slider_unavailable")
width: 1
height: 6
width: 1 * screenScaleFactor
height: 6 * screenScaleFactor
y: 0
x: qualityModel.qualitySliderStepWidth * index
}
@ -260,18 +260,18 @@ Item
{
//Draw Available line
groove: Rectangle {
implicitHeight: 2
implicitHeight: 2 * screenScaleFactor
color: UM.Theme.getColor("quality_slider_available")
radius: 1
radius: 1 * screenScaleFactor
}
handle: Item {
Rectangle {
id: qualityhandleButton
anchors.centerIn: parent
color: control.enabled ? UM.Theme.getColor("quality_slider_available") : UM.Theme.getColor("quality_slider_unavailable")
implicitWidth: 10
implicitHeight: 10
radius: 10
implicitWidth: 10 * screenScaleFactor
implicitHeight: 10 * screenScaleFactor
radius: 10 * screenScaleFactor
}
}
}
@ -309,7 +309,7 @@ Item
text: catalog.i18nc("@label", "Slower")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
color: (qualityModel.availableTotalTicks > 0) ? UM.Theme.getColor("quality_slider_available") : UM.Theme.getColor("quality_slider_unavailable")
horizontalAlignment: Text.AlignLeft
}
@ -320,7 +320,7 @@ Item
text: catalog.i18nc("@label", "Faster")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
color: (qualityModel.availableTotalTicks > 0) ? UM.Theme.getColor("quality_slider_available") : UM.Theme.getColor("quality_slider_unavailable")
horizontalAlignment: Text.AlignRight
}
}
@ -370,7 +370,7 @@ Item
//anchors.top: parent.top
anchors.left: infillSlider.left
anchors.leftMargin: (infillSlider.value / infillSlider.stepSize) * (infillSlider.width / (infillSlider.maximumValue / infillSlider.stepSize)) - 10
anchors.leftMargin: (infillSlider.value / infillSlider.stepSize) * (infillSlider.width / (infillSlider.maximumValue / infillSlider.stepSize)) - 10 * screenScaleFactor
anchors.right: parent.right
text: infillSlider.value + "%"
@ -411,8 +411,8 @@ Item
{
groove: Rectangle {
id: groove
implicitWidth: 200
implicitHeight: 2
implicitWidth: 200 * screenScaleFactor
implicitHeight: 2 * screenScaleFactor
color: control.enabled ? UM.Theme.getColor("quality_slider_available") : UM.Theme.getColor("quality_slider_unavailable")
radius: 1
}
@ -422,9 +422,9 @@ Item
id: handleButton
anchors.centerIn: parent
color: control.enabled ? UM.Theme.getColor("quality_slider_available") : UM.Theme.getColor("quality_slider_unavailable")
implicitWidth: 10
implicitHeight: 10
radius: 10
implicitWidth: 10 * screenScaleFactor
implicitHeight: 10 * screenScaleFactor
radius: 10 * screenScaleFactor
}
}
@ -446,8 +446,8 @@ Item
Rectangle {
anchors.verticalCenter: parent.verticalCenter
color: control.enabled ? UM.Theme.getColor("quality_slider_available") : UM.Theme.getColor("quality_slider_unavailable")
width: 1
height: 6
width: 1 * screenScaleFactor
height: 6 * screenScaleFactor
y: 0
x: styleData.handleWidth / 2 + index * ((repeater.width - styleData.handleWidth) / (repeater.count-1))
visible: shouldShowTick(index)
@ -500,7 +500,7 @@ Item
UM.RecolorImage {
anchors.fill: parent
anchors.margins: 2
anchors.margins: 2 * screenScaleFactor
sourceSize.width: width
sourceSize.height: width
source: UM.Theme.getIcon(model.icon)
@ -516,7 +516,7 @@ Item
property alias _hovered: enableGradualInfillMouseArea.containsMouse
anchors.top: infillSlider.bottom
anchors.topMargin: UM.Theme.getSize("sidebar_margin").height
anchors.topMargin: UM.Theme.getSize("sidebar_margin").height / 2 // closer to slider since it belongs to the same category
anchors.left: infillCellRight.left
style: UM.Theme.styles.checkbox
@ -609,7 +609,7 @@ Item
visible: enableSupportCheckBox.visible
anchors.top: infillCellRight.bottom
anchors.topMargin: UM.Theme.getSize("sidebar_margin").height
anchors.topMargin: UM.Theme.getSize("sidebar_margin").height * 1.5
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("sidebar_margin").width
anchors.verticalCenter: enableSupportCheckBox.verticalCenter
@ -624,8 +624,7 @@ Item
id: enableSupportCheckBox
property alias _hovered: enableSupportMouseArea.containsMouse
anchors.top: infillCellRight.bottom
anchors.topMargin: UM.Theme.getSize("sidebar_margin").height
anchors.top: enableSupportLabel.top
anchors.left: infillCellRight.left
style: UM.Theme.styles.checkbox;
@ -737,15 +736,19 @@ Item
{
id: adhesionHelperLabel
visible: adhesionCheckBox.visible
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("sidebar_margin").width
anchors.right: infillCellLeft.right
anchors.rightMargin: UM.Theme.getSize("sidebar_margin").width
anchors.verticalCenter: adhesionCheckBox.verticalCenter
text: catalog.i18nc("@label", "Build Plate Adhesion");
font: UM.Theme.getFont("default");
color: UM.Theme.getColor("text");
text: catalog.i18nc("@label", "Build Plate Adhesion")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
elide: Text.ElideRight
anchors {
left: parent.left
leftMargin: UM.Theme.getSize("sidebar_margin").width
right: infillCellLeft.right
rightMargin: UM.Theme.getSize("sidebar_margin").width
verticalCenter: adhesionCheckBox.verticalCenter
}
}
CheckBox

View File

@ -67,7 +67,7 @@ Item
}
}
Item { height: UM.Theme.getSize("default_margin").height; width: 1; visible: extruders.count > 0 }
Item { height: UM.Theme.getSize("default_margin").height; width: UM.Theme.getSize("default_lining").width; visible: extruders.count > 0 }
Repeater
{

View File

@ -13,10 +13,12 @@ UM.Dialog
{
title: catalog.i18nc("@title:window", "Save Project")
width: 500
height: 400
minimumWidth: 500 * screenScaleFactor
minimumHeight: 400 * screenScaleFactor
width: minimumWidth
height: minimumHeight
property int spacerHeight: 10
property int spacerHeight: 10 * screenScaleFactor
property bool dontShowAgain: true
@ -63,7 +65,7 @@ UM.Dialog
Column
{
anchors.fill: parent
spacing: 2
spacing: 2 * screenScaleFactor
Label
{
id: titleLabel

View File

@ -309,7 +309,7 @@ QtObject {
}
Behavior on color { ColorAnimation { duration: 50; } }
border.width: (control.hasOwnProperty("needBorder") && control.needBorder) ? 2 : 0
border.width: (control.hasOwnProperty("needBorder") && control.needBorder) ? 2 * screenScaleFactor : 0
border.color: Theme.getColor("tool_button_border")
UM.RecolorImage {
@ -506,8 +506,8 @@ QtObject {
source: control.iconSource;
width: Theme.getSize("section_icon").width;
height: Theme.getSize("section_icon").height;
sourceSize.width: width + 15
sourceSize.height: width + 15
sourceSize.width: width + 15 * screenScaleFactor
sourceSize.height: width + 15 * screenScaleFactor
}
}
@ -648,8 +648,8 @@ QtObject {
source: Theme.getIcon("arrow_bottom")
width: Theme.getSize("standard_arrow").width
height: Theme.getSize("standard_arrow").height
sourceSize.width: width + 5
sourceSize.height: width + 5
sourceSize.width: width + 5 * screenScaleFactor
sourceSize.height: width + 5 * screenScaleFactor
color: Theme.getColor("setting_control_text");
}
@ -707,8 +707,8 @@ QtObject {
source: UM.Theme.getIcon("arrow_bottom")
width: UM.Theme.getSize("standard_arrow").width
height: UM.Theme.getSize("standard_arrow").height
sourceSize.width: width + 5
sourceSize.height: width + 5
sourceSize.width: width + 5 * screenScaleFactor
sourceSize.height: width + 5 * screenScaleFactor
color: UM.Theme.getColor("setting_control_text")
}
@ -734,8 +734,8 @@ QtObject {
UM.RecolorImage {
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width/2.5
height: parent.height/2.5
width: parent.width / 2.5
height: parent.height / 2.5
sourceSize.width: width
sourceSize.height: width
color: Theme.getColor("checkbox_mark")