Merge pull request #3 from Ultimaker/feature_manual_instances

Add support for manual instances
This commit is contained in:
Aldo Hoeben 2016-09-29 19:15:11 +02:00 committed by GitHub
commit de44ac1df9
4 changed files with 203 additions and 12 deletions

View file

@ -52,6 +52,22 @@ class DiscoverUM3Action(MachineAction):
else:
self._network_plugin.startDiscovery()
@pyqtSlot(str, str)
def removeManualPrinter(self, key, address):
if not self._network_plugin:
return
self._network_plugin.removeManualPrinter(key, address)
@pyqtSlot(str, str)
def setManualPrinter(self, key, address):
if key != "":
# This manual printer replaces a current manual printer
self._network_plugin.removeManualPrinter(key)
if address != "":
self._network_plugin.addManualPrinter(address)
def _onPrinterDiscoveryChanged(self, *args):
self._last_zeroconf_event_time = time.time()
self.printersChanged.emit()

View file

@ -11,6 +11,7 @@ Cura.MachineAction
id: base
anchors.fill: parent;
property var selectedPrinter: null
property bool completeProperties: true
property var connectingToPrinter: null
Connections
@ -66,13 +67,45 @@ Cura.MachineAction
text: catalog.i18nc("@label", "To print directly to your Ultimaker 3 printer over the network, please make sure your printer is connected to the network using a network cable or by connecting your printer to your WIFI network. If you don't connect Cura with your Ultimaker 3, you can still use a USB drive to transfer g-code files to your printer.\n\nSelect your Ultimaker 3 from the list below:")
}
Row
{
spacing: UM.Theme.getSize("default_lining").width
Button
{
id: addButton
text: catalog.i18nc("@action:button", "Add");
onClicked:
{
manualPrinterDialog.showDialog("", "");
}
}
Button
{
id: editButton
text: catalog.i18nc("@action:button", "Edit")
enabled: base.selectedPrinter != null && base.selectedPrinter.getProperty("manual") == "true"
onClicked:
{
manualPrinterDialog.showDialog(base.selectedPrinter.getKey(), base.selectedPrinter.ipAddress);
}
}
Button
{
id: removeButton
text: catalog.i18nc("@action:button", "Remove")
enabled: base.selectedPrinter != null && base.selectedPrinter.getProperty("manual") == "true"
onClicked: manager.removeManualPrinter(base.selectedPrinter.getKey(), base.selectedPrinter.ipAddress)
}
Button
{
id: rediscoverButton
text: catalog.i18nc("@title", "Refresh")
onClicked: manager.restartDiscovery()
anchors.right: parent.right
anchors.rightMargin: parent.width * 0.5
}
}
Row
@ -118,7 +151,12 @@ Cura.MachineAction
}
width: parent.width
currentIndex: -1
onCurrentIndexChanged: base.selectedPrinter = listview.model[currentIndex]
onCurrentIndexChanged:
{
base.selectedPrinter = listview.model[currentIndex];
// Only allow connecting if the printer has responded to API query since the last refresh
base.completeProperties = base.selectedPrinter != null && base.selectedPrinter.getProperty("incomplete") != "true";
}
Component.onCompleted: manager.startDiscovery()
delegate: Rectangle
{
@ -220,10 +258,68 @@ Cura.MachineAction
Button
{
text: catalog.i18nc("@action:button", "Connect")
enabled: base.selectedPrinter ? true : false
enabled: (base.selectedPrinter && base.completeProperties) ? true : false
onClicked: connectToPrinter()
}
}
}
}
UM.Dialog
{
id: manualPrinterDialog
property string printerKey
property alias addressText: addressField.text
title: catalog.i18nc("@label", "IP Address")
minimumWidth: 400 * Screen.devicePixelRatio
minimumHeight: 120 * Screen.devicePixelRatio
width: minimumWidth
height: minimumHeight
signal showDialog(string key, string address)
onShowDialog:
{
printerKey = key;
addressText = address;
addressField.selectAll();
addressField.focus = true;
manualPrinterDialog.show();
}
onAccepted:
{
manager.setManualPrinter(printerKey, addressText)
}
Column {
anchors.fill: parent
TextField {
id: addressField
width: parent.width
maximumLength: 40
validator: RegExpValidator
{
regExp: /[a-zA-Z0-9\.\-\_]*/
}
}
}
rightButtons: [
Button {
text: catalog.i18nc("@action:button","Cancel")
onClicked: manualPrinterDialog.reject()
},
Button {
text: catalog.i18nc("@action:button", "Ok")
onClicked: manualPrinterDialog.accept()
enabled: manualPrinterDialog.addressText.trim() != ""
isDefault: true
}
]
}
}

View file

@ -36,11 +36,12 @@ class AuthState(IntEnum):
## Network connected (wifi / lan) printer that uses the Ultimaker API
@signalemitter
class NetworkPrinterOutputDevice(PrinterOutputDevice):
def __init__(self, key, address, properties):
def __init__(self, key, address, properties, api_prefix):
super().__init__(key)
self._address = address
self._key = key
self._properties = properties # Properties dict as provided by zero conf
self._api_prefix = api_prefix
self._gcode = None
self._print_finished = True # _print_finsihed == False means we're halfway in a print
@ -94,8 +95,6 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
self._material_ids = [""] * self._num_extruders
self._hotend_ids = [""] * self._num_extruders
self._api_version = "1"
self._api_prefix = "/api/v" + self._api_version + "/"
self.setPriority(2) # Make sure the output device gets selected above local file output
self.setName(key)
self.setShortDescription(i18n_catalog.i18nc("@action:button", "Print over network"))
@ -187,6 +186,14 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
def getProperties(self):
return self._properties
@pyqtSlot(str, result = str)
def getProperty(self, key):
key = key.encode("utf-8")
if key in self._properties:
return self._properties.get(key, b"").decode("utf-8")
else:
return ""
## Get the unique key of this machine
# \return key String containing the key of the machine.
@pyqtSlot(result = str)

View file

@ -5,8 +5,13 @@ from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange, ServiceInfo
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 time
import json
## 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.
@ -19,6 +24,12 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin):
self._browser = None
self._printers = {}
self._api_version = "1"
self._api_prefix = "/api/v" + self._api_version + "/"
self._network_manager = QNetworkAccessManager()
self._network_manager.finished.connect(self._onNetworkRequestFinished)
# List of old printer names. This is used to ensure that a refresh of zeroconf does not needlessly forces
# authentication requests.
self._old_printers = []
@ -28,6 +39,11 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin):
self.removePrinterSignal.connect(self.removePrinter)
Application.getInstance().globalContainerStackChanged.connect(self.reCheckConnections)
# Get list of manual printers from preferences
self._preferences = Preferences.getInstance()
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(",")
addPrinterSignal = Signal()
removePrinterSignal = Signal()
printerListChanged = Signal()
@ -49,6 +65,62 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin):
self._zero_conf = Zeroconf()
self._browser = ServiceBrowser(self._zero_conf, u'_ultimaker._tcp.local.', [self._onServiceChanged])
# Look for manual instances from preference
for address in self._manual_instances:
if address:
self.addManualPrinter(address)
def addManualPrinter(self, address):
if address not in self._manual_instances:
self._manual_instances.append(address)
self._preferences.setValue("um3networkprinting/manual_instances", ",".join(self._manual_instances))
name = address
instance_name = "manual:%s" % address
properties = { b"name": name.encode("utf-8"), b"manual": b"true", b"incomplete": b"true" }
if instance_name not in self._printers:
# Add a preliminary printer instance
self.addPrinter(instance_name, address, properties)
self.checkManualPrinter(address)
def removeManualPrinter(self, key, address = None):
if key in self._printers:
if not address:
address = self._printers[key].ipAddress
self.removePrinter(key)
if address in self._manual_instances:
self._manual_instances.remove(address)
self._preferences.setValue("um3networkprinting/manual_instances", ",".join(self._manual_instances))
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")
name_request = QNetworkRequest(url)
self._network_manager.get(name_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.
if status_code == 200:
system_info = json.loads(bytes(reply.readAll()).decode("utf-8"))
address = reply.url().host()
name = ("%s (%s)" % (system_info["name"], address))
instance_name = "manual:%s" % address
properties = { b"name": name.encode("utf-8"), b"firmware_version": system_info["firmware"].encode("utf-8"), b"manual": b"true" }
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)
## Stop looking for devices on network.
def stop(self):
if self._zero_conf is not None:
@ -72,7 +144,7 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin):
## 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)
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"):